|
1 from random import choice |
|
2 import os |
|
3 import subprocess |
|
4 import urllib2 |
|
5 import shutil |
|
6 import logging |
|
7 import re |
|
8 |
|
9 from zc.buildout import UserError |
|
10 import zc.recipe.egg |
|
11 import setuptools |
|
12 |
|
13 script_template = { |
|
14 'wsgi': ''' |
|
15 |
|
16 %(relative_paths_setup)s |
|
17 import sys |
|
18 sys.path[0:0] = [ |
|
19 %(path)s, |
|
20 ] |
|
21 %(initialization)s |
|
22 import %(module_name)s |
|
23 |
|
24 application = %(module_name)s.%(attrs)s(%(arguments)s) |
|
25 ''', |
|
26 'fcgi': ''' |
|
27 |
|
28 %(relative_paths_setup)s |
|
29 import sys |
|
30 sys.path[0:0] = [ |
|
31 %(path)s, |
|
32 ] |
|
33 %(initialization)s |
|
34 import %(module_name)s |
|
35 |
|
36 %(module_name)s.%(attrs)s(%(arguments)s) |
|
37 ''' |
|
38 } |
|
39 |
|
40 |
|
41 settings_template = ''' |
|
42 import os |
|
43 |
|
44 ADMINS = ( |
|
45 # ('Your Name', 'your_email@domain.com'), |
|
46 ) |
|
47 |
|
48 MANAGERS = ADMINS |
|
49 |
|
50 DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. |
|
51 DATABASE_NAME = '%(project)s.db' |
|
52 DATABASE_USER = '' # Not used with sqlite3. |
|
53 DATABASE_PASSWORD = '' # Not used with sqlite3. |
|
54 DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. |
|
55 DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. |
|
56 |
|
57 TIME_ZONE = 'America/Chicago' |
|
58 |
|
59 LANGUAGE_CODE = 'en-us' |
|
60 |
|
61 # Absolute path to the directory that holds media. |
|
62 # Example: "/home/media/media.lawrence.com/" |
|
63 MEDIA_ROOT = %(media_root)s |
|
64 |
|
65 # URL that handles the media served from MEDIA_ROOT. Make sure to use a |
|
66 # trailing slash if there is a path component (optional in other cases). |
|
67 # Examples: "http://media.lawrence.com", "http://example.com/media/" |
|
68 MEDIA_URL = '/media/' |
|
69 |
|
70 # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a |
|
71 # trailing slash. |
|
72 # Examples: "http://foo.com/media/", "/media/". |
|
73 ADMIN_MEDIA_PREFIX = '/admin_media/' |
|
74 |
|
75 # Don't share this with anybody. |
|
76 SECRET_KEY = '%(secret)s' |
|
77 |
|
78 MIDDLEWARE_CLASSES = ( |
|
79 'django.middleware.common.CommonMiddleware', |
|
80 'django.contrib.sessions.middleware.SessionMiddleware', |
|
81 'django.contrib.auth.middleware.AuthenticationMiddleware', |
|
82 'django.middleware.doc.XViewMiddleware', |
|
83 ) |
|
84 |
|
85 ROOT_URLCONF = '%(urlconf)s' |
|
86 |
|
87 |
|
88 INSTALLED_APPS = ( |
|
89 'django.contrib.auth', |
|
90 'django.contrib.contenttypes', |
|
91 'django.contrib.sessions', |
|
92 'django.contrib.admin', |
|
93 ) |
|
94 |
|
95 TEMPLATE_LOADERS = ( |
|
96 'django.template.loaders.filesystem.load_template_source', |
|
97 'django.template.loaders.app_directories.load_template_source', |
|
98 ) |
|
99 |
|
100 TEMPLATE_DIRS = ( |
|
101 os.path.join(os.path.dirname(__file__), "templates"), |
|
102 ) |
|
103 |
|
104 |
|
105 ''' |
|
106 |
|
107 production_settings = ''' |
|
108 from %(project)s.settings import * |
|
109 ''' |
|
110 |
|
111 development_settings = ''' |
|
112 from %(project)s.settings import * |
|
113 DEBUG=True |
|
114 TEMPLATE_DEBUG=DEBUG |
|
115 ''' |
|
116 |
|
117 urls_template = ''' |
|
118 from django.conf.urls.defaults import patterns, include, handler500 |
|
119 from django.conf import settings |
|
120 from django.contrib import admin |
|
121 admin.autodiscover() |
|
122 |
|
123 handler500 # Pyflakes |
|
124 |
|
125 urlpatterns = patterns( |
|
126 '', |
|
127 (r'^admin/(.*)', admin.site.root), |
|
128 (r'^accounts/login/$', 'django.contrib.auth.views.login'), |
|
129 ) |
|
130 |
|
131 if settings.DEBUG: |
|
132 urlpatterns += patterns('', |
|
133 (r'^media/(?P<path>.*)$', 'django.views.static.serve', |
|
134 {'document_root': settings.MEDIA_ROOT}), |
|
135 ) |
|
136 ''' |
|
137 |
|
138 class Recipe(object): |
|
139 def __init__(self, buildout, name, options): |
|
140 self.log = logging.getLogger(name) |
|
141 self.egg = zc.recipe.egg.Egg(buildout, options['recipe'], options) |
|
142 |
|
143 self.buildout, self.name, self.options = buildout, name, options |
|
144 options['location'] = os.path.join( |
|
145 buildout['buildout']['parts-directory'], name) |
|
146 options['bin-directory'] = buildout['buildout']['bin-directory'] |
|
147 |
|
148 options.setdefault('project', 'project') |
|
149 options.setdefault('settings', 'development') |
|
150 |
|
151 options.setdefault('urlconf', options['project'] + '.urls') |
|
152 options.setdefault( |
|
153 'media_root', |
|
154 "os.path.join(os.path.dirname(__file__), 'media')") |
|
155 # Set this so the rest of the recipe can expect the values to be |
|
156 # there. We need to make sure that both pythonpath and extra-paths are |
|
157 # set for BBB. |
|
158 if 'extra-paths' in options: |
|
159 options['pythonpath'] = options['extra-paths'] |
|
160 else: |
|
161 options.setdefault('extra-paths', options.get('pythonpath', '')) |
|
162 |
|
163 # Usefull when using archived versions |
|
164 buildout['buildout'].setdefault( |
|
165 'download-cache', |
|
166 os.path.join(buildout['buildout']['directory'], |
|
167 'downloads')) |
|
168 |
|
169 # mod_wsgi support script |
|
170 options.setdefault('wsgi', 'false') |
|
171 options.setdefault('fcgi', 'false') |
|
172 options.setdefault('wsgilog', '') |
|
173 options.setdefault('logfile', '') |
|
174 |
|
175 # only try to download stuff if we aren't asked to install from cache |
|
176 self.install_from_cache = self.buildout['buildout'].get( |
|
177 'install-from-cache', '').strip() == 'true' |
|
178 |
|
179 |
|
180 def install(self): |
|
181 location = self.options['location'] |
|
182 base_dir = self.buildout['buildout']['directory'] |
|
183 |
|
184 project_dir = os.path.join(base_dir, self.options['project']) |
|
185 |
|
186 download_dir = self.buildout['buildout']['download-cache'] |
|
187 if not os.path.exists(download_dir): |
|
188 os.mkdir(download_dir) |
|
189 |
|
190 version = self.options['version'] |
|
191 # Remove a pre-existing installation if it is there |
|
192 if os.path.exists(location): |
|
193 shutil.rmtree(location) |
|
194 |
|
195 if self.is_svn_url(version): |
|
196 self.install_svn_version(version, download_dir, location, |
|
197 self.install_from_cache) |
|
198 else: |
|
199 tarball = self.get_release(version, download_dir) |
|
200 # Extract and put the dir in its proper place |
|
201 self.install_release(version, download_dir, tarball, location) |
|
202 |
|
203 self.options['setup'] = location |
|
204 development = zc.recipe.egg.Develop(self.buildout, |
|
205 self.options['recipe'], |
|
206 self.options) |
|
207 development.install() |
|
208 del self.options['setup'] |
|
209 |
|
210 extra_paths = self.get_extra_paths() |
|
211 requirements, ws = self.egg.working_set(['djangorecipe']) |
|
212 |
|
213 script_paths = [] |
|
214 |
|
215 # Create the Django management script |
|
216 script_paths.extend(self.create_manage_script(extra_paths, ws)) |
|
217 |
|
218 # Create the test runner |
|
219 script_paths.extend(self.create_test_runner(extra_paths, ws)) |
|
220 |
|
221 # Make the wsgi and fastcgi scripts if enabled |
|
222 script_paths.extend(self.make_scripts(extra_paths, ws)) |
|
223 |
|
224 # Create default settings if we haven't got a project |
|
225 # egg specified, and if it doesn't already exist |
|
226 if not self.options.get('projectegg'): |
|
227 if not os.path.exists(project_dir): |
|
228 self.create_project(project_dir) |
|
229 else: |
|
230 self.log.info( |
|
231 'Skipping creating of project: %(project)s since ' |
|
232 'it exists' % self.options) |
|
233 |
|
234 return script_paths + [location] |
|
235 |
|
236 def install_svn_version(self, version, download_dir, location, |
|
237 install_from_cache): |
|
238 svn_url = self.version_to_svn(version) |
|
239 download_location = os.path.join( |
|
240 download_dir, 'django-' + |
|
241 self.version_to_download_suffix(version)) |
|
242 if not install_from_cache: |
|
243 if os.path.exists(download_location): |
|
244 if self.svn_update(download_location, version): |
|
245 raise UserError( |
|
246 "Failed to update Django; %s. " |
|
247 "Please check your internet connection." % ( |
|
248 download_location)) |
|
249 else: |
|
250 self.log.info("Checking out Django from svn: %s" % svn_url) |
|
251 cmd = 'svn co %s %s' % (svn_url, download_location) |
|
252 if not self.buildout['buildout'].get('verbosity'): |
|
253 cmd += ' -q' |
|
254 if self.command(cmd): |
|
255 raise UserError("Failed to checkout Django. " |
|
256 "Please check your internet connection.") |
|
257 else: |
|
258 self.log.info("Installing Django from cache: " + download_location) |
|
259 |
|
260 shutil.copytree(download_location, location) |
|
261 |
|
262 |
|
263 def install_release(self, version, download_dir, tarball, destination): |
|
264 extraction_dir = os.path.join(download_dir, 'django-archive') |
|
265 setuptools.archive_util.unpack_archive(tarball, extraction_dir) |
|
266 # Lookup the resulting extraction dir instead of guessing it |
|
267 # (Django releases have a tendency not to be consistend here) |
|
268 untarred_dir = os.path.join(extraction_dir, |
|
269 os.listdir(extraction_dir)[0]) |
|
270 shutil.move(untarred_dir, destination) |
|
271 shutil.rmtree(extraction_dir) |
|
272 |
|
273 def get_release(self, version, download_dir): |
|
274 tarball = os.path.join(download_dir, 'django-%s.tar.gz' % version) |
|
275 |
|
276 # Only download when we don't yet have an archive |
|
277 if not os.path.exists(tarball): |
|
278 download_url = 'http://www.djangoproject.com/download/%s/tarball/' |
|
279 self.log.info("Downloading Django from: %s" % ( |
|
280 download_url % version)) |
|
281 |
|
282 tarball_f = open(tarball, 'wb') |
|
283 f = urllib2.urlopen(download_url % version) |
|
284 tarball_f.write(f.read()) |
|
285 tarball_f.close() |
|
286 f.close() |
|
287 return tarball |
|
288 |
|
289 def create_manage_script(self, extra_paths, ws): |
|
290 project = self.options.get('projectegg', self.options['project']) |
|
291 return zc.buildout.easy_install.scripts( |
|
292 [(self.options.get('control-script', self.name), |
|
293 'djangorecipe.manage', 'main')], |
|
294 ws, self.options['executable'], self.options['bin-directory'], |
|
295 extra_paths = extra_paths, |
|
296 arguments= "'%s.%s'" % (project, |
|
297 self.options['settings'])) |
|
298 |
|
299 |
|
300 |
|
301 def create_test_runner(self, extra_paths, working_set): |
|
302 apps = self.options.get('test', '').split() |
|
303 # Only create the testrunner if the user requests it |
|
304 if apps: |
|
305 return zc.buildout.easy_install.scripts( |
|
306 [(self.options.get('testrunner', 'test'), |
|
307 'djangorecipe.test', 'main')], |
|
308 working_set, self.options['executable'], |
|
309 self.options['bin-directory'], |
|
310 extra_paths = extra_paths, |
|
311 arguments= "'%s.%s', %s" % ( |
|
312 self.options['project'], |
|
313 self.options['settings'], |
|
314 ', '.join(["'%s'" % app for app in apps]))) |
|
315 else: |
|
316 return [] |
|
317 |
|
318 |
|
319 def create_project(self, project_dir): |
|
320 os.makedirs(project_dir) |
|
321 |
|
322 template_vars = {'secret': self.generate_secret()} |
|
323 template_vars.update(self.options) |
|
324 |
|
325 self.create_file( |
|
326 os.path.join(project_dir, 'development.py'), |
|
327 development_settings, template_vars) |
|
328 |
|
329 self.create_file( |
|
330 os.path.join(project_dir, 'production.py'), |
|
331 production_settings, template_vars) |
|
332 |
|
333 self.create_file( |
|
334 os.path.join(project_dir, 'urls.py'), |
|
335 urls_template, template_vars) |
|
336 |
|
337 self.create_file( |
|
338 os.path.join(project_dir, 'settings.py'), |
|
339 settings_template, template_vars) |
|
340 |
|
341 # Create the media and templates directories for our |
|
342 # project |
|
343 os.mkdir(os.path.join(project_dir, 'media')) |
|
344 os.mkdir(os.path.join(project_dir, 'templates')) |
|
345 |
|
346 # Make the settings dir a Python package so that Django |
|
347 # can load the settings from it. It will act like the |
|
348 # project dir. |
|
349 open(os.path.join(project_dir, '__init__.py'), 'w').close() |
|
350 |
|
351 def make_scripts(self, extra_paths, ws): |
|
352 scripts = [] |
|
353 _script_template = zc.buildout.easy_install.script_template |
|
354 for protocol in ('wsgi', 'fcgi'): |
|
355 zc.buildout.easy_install.script_template = \ |
|
356 zc.buildout.easy_install.script_header + \ |
|
357 script_template[protocol] |
|
358 if self.options.get(protocol, '').lower() == 'true': |
|
359 project = self.options.get('projectegg', |
|
360 self.options['project']) |
|
361 scripts.extend( |
|
362 zc.buildout.easy_install.scripts( |
|
363 [('%s.%s' % (self.options.get('control-script', |
|
364 self.name), |
|
365 protocol), |
|
366 'djangorecipe.%s' % protocol, 'main')], |
|
367 ws, |
|
368 self.options['executable'], |
|
369 self.options['bin-directory'], |
|
370 extra_paths=extra_paths, |
|
371 arguments= "'%s.%s', logfile='%s'" % ( |
|
372 project, self.options['settings'], |
|
373 self.options.get('logfile')))) |
|
374 zc.buildout.easy_install.script_template = _script_template |
|
375 return scripts |
|
376 |
|
377 def is_svn_url(self, version): |
|
378 # Search if there is http/https/svn or svn+[a tunnel identifier] in the |
|
379 # url or if the trunk marker is used, all indicating the use of svn |
|
380 svn_version_search = re.compile( |
|
381 r'^(http|https|svn|svn\+[a-zA-Z-_]+)://|^(trunk)$').search(version) |
|
382 return svn_version_search is not None |
|
383 |
|
384 def version_to_svn(self, version): |
|
385 if version == 'trunk': |
|
386 return 'http://code.djangoproject.com/svn/django/trunk/' |
|
387 else: |
|
388 return version |
|
389 |
|
390 def version_to_download_suffix(self, version): |
|
391 if version == 'trunk': |
|
392 return 'svn' |
|
393 return [p for p in version.split('/') if p][-1] |
|
394 |
|
395 def svn_update(self, path, version): |
|
396 command = 'svn up' |
|
397 revision_search = re.compile(r'@([0-9]*)$').search( |
|
398 self.options['version']) |
|
399 |
|
400 if revision_search is not None: |
|
401 command += ' -r ' + revision_search.group(1) |
|
402 self.log.info("Updating Django from svn") |
|
403 if not self.buildout['buildout'].get('verbosity'): |
|
404 command += ' -q' |
|
405 return self.command(command, cwd=path) |
|
406 |
|
407 def get_extra_paths(self): |
|
408 extra_paths = [self.options['location'], |
|
409 self.buildout['buildout']['directory'] |
|
410 ] |
|
411 |
|
412 # Add libraries found by a site .pth files to our extra-paths. |
|
413 if 'pth-files' in self.options: |
|
414 import site |
|
415 for pth_file in self.options['pth-files'].splitlines(): |
|
416 pth_libs = site.addsitedir(pth_file, set()) |
|
417 if not pth_libs: |
|
418 self.log.warning( |
|
419 "No site *.pth libraries found for pth_file=%s" % ( |
|
420 pth_file,)) |
|
421 else: |
|
422 self.log.info("Adding *.pth libraries=%s" % pth_libs) |
|
423 self.options['extra-paths'] += '\n' + '\n'.join(pth_libs) |
|
424 |
|
425 pythonpath = [p.replace('/', os.path.sep) for p in |
|
426 self.options['extra-paths'].splitlines() if p.strip()] |
|
427 |
|
428 extra_paths.extend(pythonpath) |
|
429 return extra_paths |
|
430 |
|
431 def update(self): |
|
432 newest = self.buildout['buildout'].get('newest') != 'false' |
|
433 if newest and not self.install_from_cache and \ |
|
434 self.is_svn_url(self.options['version']): |
|
435 self.svn_update(self.options['location'], self.options['version']) |
|
436 |
|
437 extra_paths = self.get_extra_paths() |
|
438 requirements, ws = self.egg.working_set(['djangorecipe']) |
|
439 # Create the Django management script |
|
440 self.create_manage_script(extra_paths, ws) |
|
441 |
|
442 # Create the test runner |
|
443 self.create_test_runner(extra_paths, ws) |
|
444 |
|
445 # Make the wsgi and fastcgi scripts if enabled |
|
446 self.make_scripts(extra_paths, ws) |
|
447 |
|
448 def command(self, cmd, **kwargs): |
|
449 output = subprocess.PIPE |
|
450 if self.buildout['buildout'].get('verbosity'): |
|
451 output = None |
|
452 command = subprocess.Popen( |
|
453 cmd, shell=True, stdout=output, **kwargs) |
|
454 return command.wait() |
|
455 |
|
456 def create_file(self, file, template, options): |
|
457 if os.path.exists(file): |
|
458 return |
|
459 |
|
460 f = open(file, 'w') |
|
461 f.write(template % options) |
|
462 f.close() |
|
463 |
|
464 def generate_secret(self): |
|
465 chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' |
|
466 return ''.join([choice(chars) for i in range(50)]) |