from random import choice import os import subprocess import urllib2 import shutil import logging import re from zc.buildout import UserError import zc.recipe.egg import setuptools script_template = { 'wsgi': ''' %(relative_paths_setup)s import sys sys.path[0:0] = [ %(path)s, ] %(initialization)s import %(module_name)s application = %(module_name)s.%(attrs)s(%(arguments)s) ''', 'fcgi': ''' %(relative_paths_setup)s import sys sys.path[0:0] = [ %(path)s, ] %(initialization)s import %(module_name)s %(module_name)s.%(attrs)s(%(arguments)s) ''' } settings_template = ''' import os ADMINS = ( # ('Your Name', 'your_email@domain.com'), ) MANAGERS = ADMINS DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. DATABASE_NAME = '%(project)s.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. TIME_ZONE = 'America/Chicago' LANGUAGE_CODE = 'en-us' # Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" MEDIA_ROOT = %(media_root)s # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). # Examples: "http://media.lawrence.com", "http://example.com/media/" MEDIA_URL = '/media/' # 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 = '/admin_media/' # Don't share this with anybody. SECRET_KEY = '%(secret)s' MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.middleware.doc.XViewMiddleware', ) ROOT_URLCONF = '%(urlconf)s' INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.admin', ) TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.load_template_source', 'django.template.loaders.app_directories.load_template_source', ) TEMPLATE_DIRS = ( os.path.join(os.path.dirname(__file__), "templates"), ) ''' production_settings = ''' from %(project)s.settings import * ''' development_settings = ''' from %(project)s.settings import * DEBUG=True TEMPLATE_DEBUG=DEBUG ''' urls_template = ''' from django.conf.urls.defaults import patterns, include, handler500 from django.conf import settings from django.contrib import admin admin.autodiscover() handler500 # Pyflakes urlpatterns = patterns( '', (r'^admin/(.*)', admin.site.root), (r'^accounts/login/$', 'django.contrib.auth.views.login'), ) if settings.DEBUG: urlpatterns += patterns('', (r'^media/(?P.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}), ) ''' class Recipe(object): def __init__(self, buildout, name, options): self.log = logging.getLogger(name) self.egg = zc.recipe.egg.Egg(buildout, options['recipe'], options) self.buildout, self.name, self.options = buildout, name, options options['location'] = os.path.join( buildout['buildout']['parts-directory'], name) options['bin-directory'] = buildout['buildout']['bin-directory'] options.setdefault('project', 'project') options.setdefault('settings', 'development') options.setdefault('urlconf', options['project'] + '.urls') options.setdefault( 'media_root', "os.path.join(os.path.dirname(__file__), 'media')") # Set this so the rest of the recipe can expect the values to be # there. We need to make sure that both pythonpath and extra-paths are # set for BBB. if 'extra-paths' in options: options['pythonpath'] = options['extra-paths'] else: options.setdefault('extra-paths', options.get('pythonpath', '')) # Usefull when using archived versions buildout['buildout'].setdefault( 'download-cache', os.path.join(buildout['buildout']['directory'], 'downloads')) # mod_wsgi support script options.setdefault('wsgi', 'false') options.setdefault('fcgi', 'false') options.setdefault('wsgilog', '') options.setdefault('logfile', '') # only try to download stuff if we aren't asked to install from cache self.install_from_cache = self.buildout['buildout'].get( 'install-from-cache', '').strip() == 'true' def install(self): location = self.options['location'] base_dir = self.buildout['buildout']['directory'] project_dir = os.path.join(base_dir, self.options['project']) download_dir = self.buildout['buildout']['download-cache'] if not os.path.exists(download_dir): os.mkdir(download_dir) version = self.options['version'] # Remove a pre-existing installation if it is there if os.path.exists(location): shutil.rmtree(location) if self.is_svn_url(version): self.install_svn_version(version, download_dir, location, self.install_from_cache) else: tarball = self.get_release(version, download_dir) # Extract and put the dir in its proper place self.install_release(version, download_dir, tarball, location) self.options['setup'] = location development = zc.recipe.egg.Develop(self.buildout, self.options['recipe'], self.options) development.install() del self.options['setup'] extra_paths = self.get_extra_paths() requirements, ws = self.egg.working_set(['djangorecipe']) script_paths = [] # Create the Django management script script_paths.extend(self.create_manage_script(extra_paths, ws)) # Create the test runner script_paths.extend(self.create_test_runner(extra_paths, ws)) # Make the wsgi and fastcgi scripts if enabled script_paths.extend(self.make_scripts(extra_paths, ws)) # Create default settings if we haven't got a project # egg specified, and if it doesn't already exist if not self.options.get('projectegg'): if not os.path.exists(project_dir): self.create_project(project_dir) else: self.log.info( 'Skipping creating of project: %(project)s since ' 'it exists' % self.options) return script_paths + [location] def install_svn_version(self, version, download_dir, location, install_from_cache): svn_url = self.version_to_svn(version) download_location = os.path.join( download_dir, 'django-' + self.version_to_download_suffix(version)) if not install_from_cache: if os.path.exists(download_location): if self.svn_update(download_location, version): raise UserError( "Failed to update Django; %s. " "Please check your internet connection." % ( download_location)) else: self.log.info("Checking out Django from svn: %s" % svn_url) cmd = 'svn co %s %s' % (svn_url, download_location) if not self.buildout['buildout'].get('verbosity'): cmd += ' -q' if self.command(cmd): raise UserError("Failed to checkout Django. " "Please check your internet connection.") else: self.log.info("Installing Django from cache: " + download_location) shutil.copytree(download_location, location) def install_release(self, version, download_dir, tarball, destination): extraction_dir = os.path.join(download_dir, 'django-archive') setuptools.archive_util.unpack_archive(tarball, extraction_dir) # Lookup the resulting extraction dir instead of guessing it # (Django releases have a tendency not to be consistend here) untarred_dir = os.path.join(extraction_dir, os.listdir(extraction_dir)[0]) shutil.move(untarred_dir, destination) shutil.rmtree(extraction_dir) def get_release(self, version, download_dir): tarball = os.path.join(download_dir, 'django-%s.tar.gz' % version) # Only download when we don't yet have an archive if not os.path.exists(tarball): download_url = 'http://www.djangoproject.com/download/%s/tarball/' self.log.info("Downloading Django from: %s" % ( download_url % version)) tarball_f = open(tarball, 'wb') f = urllib2.urlopen(download_url % version) tarball_f.write(f.read()) tarball_f.close() f.close() return tarball def create_manage_script(self, extra_paths, ws): project = self.options.get('projectegg', self.options['project']) return zc.buildout.easy_install.scripts( [(self.options.get('control-script', self.name), 'djangorecipe.manage', 'main')], ws, self.options['executable'], self.options['bin-directory'], extra_paths = extra_paths, arguments= "'%s.%s'" % (project, self.options['settings'])) def create_test_runner(self, extra_paths, working_set): apps = self.options.get('test', '').split() # Only create the testrunner if the user requests it if apps: return zc.buildout.easy_install.scripts( [(self.options.get('testrunner', 'test'), 'djangorecipe.test', 'main')], working_set, self.options['executable'], self.options['bin-directory'], extra_paths = extra_paths, arguments= "'%s.%s', %s" % ( self.options['project'], self.options['settings'], ', '.join(["'%s'" % app for app in apps]))) else: return [] def create_project(self, project_dir): os.makedirs(project_dir) template_vars = {'secret': self.generate_secret()} template_vars.update(self.options) self.create_file( os.path.join(project_dir, 'development.py'), development_settings, template_vars) self.create_file( os.path.join(project_dir, 'production.py'), production_settings, template_vars) self.create_file( os.path.join(project_dir, 'urls.py'), urls_template, template_vars) self.create_file( os.path.join(project_dir, 'settings.py'), settings_template, template_vars) # Create the media and templates directories for our # project os.mkdir(os.path.join(project_dir, 'media')) os.mkdir(os.path.join(project_dir, 'templates')) # Make the settings dir a Python package so that Django # can load the settings from it. It will act like the # project dir. open(os.path.join(project_dir, '__init__.py'), 'w').close() def make_scripts(self, extra_paths, ws): scripts = [] _script_template = zc.buildout.easy_install.script_template for protocol in ('wsgi', 'fcgi'): zc.buildout.easy_install.script_template = \ zc.buildout.easy_install.script_header + \ script_template[protocol] if self.options.get(protocol, '').lower() == 'true': project = self.options.get('projectegg', self.options['project']) scripts.extend( zc.buildout.easy_install.scripts( [('%s.%s' % (self.options.get('control-script', self.name), protocol), 'djangorecipe.%s' % protocol, 'main')], ws, self.options['executable'], self.options['bin-directory'], extra_paths=extra_paths, arguments= "'%s.%s', logfile='%s'" % ( project, self.options['settings'], self.options.get('logfile')))) zc.buildout.easy_install.script_template = _script_template return scripts def is_svn_url(self, version): # Search if there is http/https/svn or svn+[a tunnel identifier] in the # url or if the trunk marker is used, all indicating the use of svn svn_version_search = re.compile( r'^(http|https|svn|svn\+[a-zA-Z-_]+)://|^(trunk)$').search(version) return svn_version_search is not None def version_to_svn(self, version): if version == 'trunk': return 'http://code.djangoproject.com/svn/django/trunk/' else: return version def version_to_download_suffix(self, version): if version == 'trunk': return 'svn' return [p for p in version.split('/') if p][-1] def svn_update(self, path, version): command = 'svn up' revision_search = re.compile(r'@([0-9]*)$').search( self.options['version']) if revision_search is not None: command += ' -r ' + revision_search.group(1) self.log.info("Updating Django from svn") if not self.buildout['buildout'].get('verbosity'): command += ' -q' return self.command(command, cwd=path) def get_extra_paths(self): extra_paths = [self.options['location'], self.buildout['buildout']['directory'] ] # Add libraries found by a site .pth files to our extra-paths. if 'pth-files' in self.options: import site for pth_file in self.options['pth-files'].splitlines(): pth_libs = site.addsitedir(pth_file, set()) if not pth_libs: self.log.warning( "No site *.pth libraries found for pth_file=%s" % ( pth_file,)) else: self.log.info("Adding *.pth libraries=%s" % pth_libs) self.options['extra-paths'] += '\n' + '\n'.join(pth_libs) pythonpath = [p.replace('/', os.path.sep) for p in self.options['extra-paths'].splitlines() if p.strip()] extra_paths.extend(pythonpath) return extra_paths def update(self): newest = self.buildout['buildout'].get('newest') != 'false' if newest and not self.install_from_cache and \ self.is_svn_url(self.options['version']): self.svn_update(self.options['location'], self.options['version']) extra_paths = self.get_extra_paths() requirements, ws = self.egg.working_set(['djangorecipe']) # Create the Django management script self.create_manage_script(extra_paths, ws) # Create the test runner self.create_test_runner(extra_paths, ws) # Make the wsgi and fastcgi scripts if enabled self.make_scripts(extra_paths, ws) def command(self, cmd, **kwargs): output = subprocess.PIPE if self.buildout['buildout'].get('verbosity'): output = None command = subprocess.Popen( cmd, shell=True, stdout=output, **kwargs) return command.wait() def create_file(self, file, template, options): if os.path.exists(file): return f = open(file, 'w') f.write(template % options) f.close() def generate_secret(self): chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' return ''.join([choice(chars) for i in range(50)])