App Engine Python SDK version 1.7.4 (2)
[gae.git] / python / lib / django_1_4 / django / core / management / templates.py
blob9a4b2c1723ff4f2a543d4eabb89fb628f6f2d66f
1 from __future__ import with_statement
2 import cgi
3 import errno
4 import mimetypes
5 import os
6 import posixpath
7 import re
8 import shutil
9 import stat
10 import sys
11 import tempfile
12 import urllib
14 from optparse import make_option
15 from os import path
17 import django
18 from django.template import Template, Context
19 from django.utils import archive
20 from django.utils.encoding import smart_str
21 from django.utils._os import rmtree_errorhandler
22 from django.core.management.base import BaseCommand, CommandError
23 from django.core.management.commands.makemessages import handle_extensions
26 _drive_re = re.compile('^([a-z]):', re.I)
27 _url_drive_re = re.compile('^([a-z])[:|]', re.I)
30 class TemplateCommand(BaseCommand):
31 """
32 Copies either a Django application layout template or a Django project
33 layout template into the specified directory.
35 :param style: A color style object (see django.core.management.color).
36 :param app_or_project: The string 'app' or 'project'.
37 :param name: The name of the application or project.
38 :param directory: The directory to which the template should be copied.
39 :param options: The additional variables passed to project or app templates
40 """
41 args = "[name] [optional destination directory]"
42 option_list = BaseCommand.option_list + (
43 make_option('--template',
44 action='store', dest='template',
45 help='The dotted import path to load the template from.'),
46 make_option('--extension', '-e', dest='extensions',
47 action='append', default=['py'],
48 help='The file extension(s) to render (default: "py"). '
49 'Separate multiple extensions with commas, or use '
50 '-e multiple times.'),
51 make_option('--name', '-n', dest='files',
52 action='append', default=[],
53 help='The file name(s) to render. '
54 'Separate multiple extensions with commas, or use '
55 '-n multiple times.')
57 requires_model_validation = False
58 # Can't import settings during this command, because they haven't
59 # necessarily been created.
60 can_import_settings = False
61 # The supported URL schemes
62 url_schemes = ['http', 'https', 'ftp']
64 def handle(self, app_or_project, name, target=None, **options):
65 self.app_or_project = app_or_project
66 self.paths_to_remove = []
67 self.verbosity = int(options.get('verbosity'))
69 # If it's not a valid directory name.
70 if not re.search(r'^[_a-zA-Z]\w*$', name):
71 # Provide a smart error message, depending on the error.
72 if not re.search(r'^[_a-zA-Z]', name):
73 message = ('make sure the name begins '
74 'with a letter or underscore')
75 else:
76 message = 'use only numbers, letters and underscores'
77 raise CommandError("%r is not a valid %s name. Please %s." %
78 (name, app_or_project, message))
80 # if some directory is given, make sure it's nicely expanded
81 if target is None:
82 top_dir = path.join(os.getcwd(), name)
83 try:
84 os.makedirs(top_dir)
85 except OSError, e:
86 if e.errno == errno.EEXIST:
87 message = "'%s' already exists" % top_dir
88 else:
89 message = e
90 raise CommandError(message)
91 else:
92 top_dir = os.path.abspath(path.expanduser(target))
93 if not os.path.exists(top_dir):
94 raise CommandError("Destination directory '%s' does not "
95 "exist, please create it first." % top_dir)
97 extensions = tuple(
98 handle_extensions(options.get('extensions'), ignored=()))
99 extra_files = []
100 for file in options.get('files'):
101 extra_files.extend(map(lambda x: x.strip(), file.split(',')))
102 if self.verbosity >= 2:
103 self.stdout.write("Rendering %s template files with "
104 "extensions: %s\n" %
105 (app_or_project, ', '.join(extensions)))
106 self.stdout.write("Rendering %s template files with "
107 "filenames: %s\n" %
108 (app_or_project, ', '.join(extra_files)))
110 base_name = '%s_name' % app_or_project
111 base_subdir = '%s_template' % app_or_project
112 base_directory = '%s_directory' % app_or_project
114 context = Context(dict(options, **{
115 base_name: name,
116 base_directory: top_dir,
119 # Setup a stub settings environment for template rendering
120 from django.conf import settings
121 if not settings.configured:
122 settings.configure()
124 template_dir = self.handle_template(options.get('template'),
125 base_subdir)
126 prefix_length = len(template_dir) + 1
128 for root, dirs, files in os.walk(template_dir):
130 path_rest = root[prefix_length:]
131 relative_dir = path_rest.replace(base_name, name)
132 if relative_dir:
133 target_dir = path.join(top_dir, relative_dir)
134 if not path.exists(target_dir):
135 os.mkdir(target_dir)
137 for dirname in dirs[:]:
138 if dirname.startswith('.'):
139 dirs.remove(dirname)
141 for filename in files:
142 if filename.endswith(('.pyo', '.pyc', '.py.class')):
143 # Ignore some files as they cause various breakages.
144 continue
145 old_path = path.join(root, filename)
146 new_path = path.join(top_dir, relative_dir,
147 filename.replace(base_name, name))
148 if path.exists(new_path):
149 raise CommandError("%s already exists, overlaying a "
150 "project or app into an existing "
151 "directory won't replace conflicting "
152 "files" % new_path)
154 # Only render the Python files, as we don't want to
155 # accidentally render Django templates files
156 with open(old_path, 'r') as template_file:
157 content = template_file.read()
158 if filename.endswith(extensions) or filename in extra_files:
159 template = Template(content)
160 content = template.render(context)
161 with open(new_path, 'w') as new_file:
162 new_file.write(content)
164 if self.verbosity >= 2:
165 self.stdout.write("Creating %s\n" % new_path)
166 try:
167 shutil.copymode(old_path, new_path)
168 self.make_writeable(new_path)
169 except OSError:
170 notice = self.style.NOTICE(
171 "Notice: Couldn't set permission bits on %s. You're "
172 "probably using an uncommon filesystem setup. No "
173 "problem.\n" % new_path)
174 sys.stderr.write(smart_str(notice))
176 if self.paths_to_remove:
177 if self.verbosity >= 2:
178 self.stdout.write("Cleaning up temporary files.\n")
179 for path_to_remove in self.paths_to_remove:
180 if path.isfile(path_to_remove):
181 os.remove(path_to_remove)
182 else:
183 shutil.rmtree(path_to_remove,
184 onerror=rmtree_errorhandler)
186 def handle_template(self, template, subdir):
188 Determines where the app or project templates are.
189 Use django.__path__[0] as the default because we don't
190 know into which directory Django has been installed.
192 if template is None:
193 return path.join(django.__path__[0], 'conf', subdir)
194 else:
195 if template.startswith('file://'):
196 template = template[7:]
197 expanded_template = path.expanduser(template)
198 expanded_template = path.normpath(expanded_template)
199 if path.isdir(expanded_template):
200 return expanded_template
201 if self.is_url(template):
202 # downloads the file and returns the path
203 absolute_path = self.download(template)
204 else:
205 absolute_path = path.abspath(expanded_template)
206 if path.exists(absolute_path):
207 return self.extract(absolute_path)
209 raise CommandError("couldn't handle %s template %s." %
210 (self.app_or_project, template))
212 def download(self, url):
214 Downloads the given URL and returns the file name.
216 def cleanup_url(url):
217 tmp = url.rstrip('/')
218 filename = tmp.split('/')[-1]
219 if url.endswith('/'):
220 display_url = tmp + '/'
221 else:
222 display_url = url
223 return filename, display_url
225 prefix = 'django_%s_template_' % self.app_or_project
226 tempdir = tempfile.mkdtemp(prefix=prefix, suffix='_download')
227 self.paths_to_remove.append(tempdir)
228 filename, display_url = cleanup_url(url)
230 if self.verbosity >= 2:
231 self.stdout.write("Downloading %s\n" % display_url)
232 try:
233 the_path, info = urllib.urlretrieve(url,
234 path.join(tempdir, filename))
235 except IOError, e:
236 raise CommandError("couldn't download URL %s to %s: %s" %
237 (url, filename, e))
239 used_name = the_path.split('/')[-1]
241 # Trying to get better name from response headers
242 content_disposition = info.get('content-disposition')
243 if content_disposition:
244 _, params = cgi.parse_header(content_disposition)
245 guessed_filename = params.get('filename') or used_name
246 else:
247 guessed_filename = used_name
249 # Falling back to content type guessing
250 ext = self.splitext(guessed_filename)[1]
251 content_type = info.get('content-type')
252 if not ext and content_type:
253 ext = mimetypes.guess_extension(content_type)
254 if ext:
255 guessed_filename += ext
257 # Move the temporary file to a filename that has better
258 # chances of being recognnized by the archive utils
259 if used_name != guessed_filename:
260 guessed_path = path.join(tempdir, guessed_filename)
261 shutil.move(the_path, guessed_path)
262 return guessed_path
264 # Giving up
265 return the_path
267 def splitext(self, the_path):
269 Like os.path.splitext, but takes off .tar, too
271 base, ext = posixpath.splitext(the_path)
272 if base.lower().endswith('.tar'):
273 ext = base[-4:] + ext
274 base = base[:-4]
275 return base, ext
277 def extract(self, filename):
279 Extracts the given file to a temporarily and returns
280 the path of the directory with the extracted content.
282 prefix = 'django_%s_template_' % self.app_or_project
283 tempdir = tempfile.mkdtemp(prefix=prefix, suffix='_extract')
284 self.paths_to_remove.append(tempdir)
285 if self.verbosity >= 2:
286 self.stdout.write("Extracting %s\n" % filename)
287 try:
288 archive.extract(filename, tempdir)
289 return tempdir
290 except (archive.ArchiveException, IOError), e:
291 raise CommandError("couldn't extract file %s to %s: %s" %
292 (filename, tempdir, e))
294 def is_url(self, template):
296 Returns True if the name looks like a URL
298 if ':' not in template:
299 return False
300 scheme = template.split(':', 1)[0].lower()
301 return scheme in self.url_schemes
303 def make_writeable(self, filename):
305 Make sure that the file is writeable.
306 Useful if our source is read-only.
308 if sys.platform.startswith('java'):
309 # On Jython there is no os.access()
310 return
311 if not os.access(filename, os.W_OK):
312 st = os.stat(filename)
313 new_permissions = stat.S_IMODE(st.st_mode) | stat.S_IWUSR
314 os.chmod(filename, new_permissions)