2011-12-23 06:38:02 +08:00
|
|
|
import cgi
|
2012-01-16 00:06:56 +08:00
|
|
|
import errno
|
2011-12-23 06:38:02 +08:00
|
|
|
import mimetypes
|
|
|
|
import os
|
|
|
|
import posixpath
|
|
|
|
import re
|
|
|
|
import shutil
|
|
|
|
import stat
|
|
|
|
import sys
|
|
|
|
import tempfile
|
|
|
|
|
|
|
|
from optparse import make_option
|
|
|
|
from os import path
|
|
|
|
|
|
|
|
import django
|
|
|
|
from django.template import Template, Context
|
|
|
|
from django.utils import archive
|
2013-09-06 03:38:59 +08:00
|
|
|
from django.utils.six.moves.urllib.request import urlretrieve
|
2011-12-23 06:38:02 +08:00
|
|
|
from django.utils._os import rmtree_errorhandler
|
|
|
|
from django.core.management.base import BaseCommand, CommandError
|
2013-02-27 09:07:22 +08:00
|
|
|
from django.core.management.utils import handle_extensions
|
2011-12-23 06:38:02 +08:00
|
|
|
|
|
|
|
|
|
|
|
_drive_re = re.compile('^([a-z]):', re.I)
|
|
|
|
_url_drive_re = re.compile('^([a-z])[:|]', re.I)
|
|
|
|
|
|
|
|
|
|
|
|
class TemplateCommand(BaseCommand):
|
|
|
|
"""
|
|
|
|
Copies either a Django application layout template or a Django project
|
|
|
|
layout template into the specified directory.
|
|
|
|
|
|
|
|
:param style: A color style object (see django.core.management.color).
|
|
|
|
:param app_or_project: The string 'app' or 'project'.
|
|
|
|
:param name: The name of the application or project.
|
|
|
|
:param directory: The directory to which the template should be copied.
|
|
|
|
:param options: The additional variables passed to project or app templates
|
|
|
|
"""
|
|
|
|
args = "[name] [optional destination directory]"
|
|
|
|
option_list = BaseCommand.option_list + (
|
|
|
|
make_option('--template',
|
|
|
|
action='store', dest='template',
|
2013-05-08 15:55:40 +08:00
|
|
|
help='The path or URL to load the template from.'),
|
2011-12-23 06:38:02 +08:00
|
|
|
make_option('--extension', '-e', dest='extensions',
|
|
|
|
action='append', default=['py'],
|
2012-02-18 00:51:22 +08:00
|
|
|
help='The file extension(s) to render (default: "py"). '
|
2011-12-23 06:38:02 +08:00
|
|
|
'Separate multiple extensions with commas, or use '
|
|
|
|
'-e multiple times.'),
|
2012-02-04 21:01:30 +08:00
|
|
|
make_option('--name', '-n', dest='files',
|
|
|
|
action='append', default=[],
|
2012-02-18 00:51:22 +08:00
|
|
|
help='The file name(s) to render. '
|
2012-02-04 21:01:30 +08:00
|
|
|
'Separate multiple extensions with commas, or use '
|
|
|
|
'-n multiple times.')
|
2013-10-18 17:02:43 +08:00
|
|
|
)
|
2011-12-23 06:38:02 +08:00
|
|
|
requires_model_validation = False
|
|
|
|
# Can't import settings during this command, because they haven't
|
|
|
|
# necessarily been created.
|
|
|
|
can_import_settings = False
|
|
|
|
# The supported URL schemes
|
|
|
|
url_schemes = ['http', 'https', 'ftp']
|
2013-02-04 07:53:48 +08:00
|
|
|
# Can't perform any active locale changes during this command, because
|
|
|
|
# setting might not be available at all.
|
|
|
|
leave_locale_alone = True
|
2011-12-23 06:38:02 +08:00
|
|
|
|
|
|
|
def handle(self, app_or_project, name, target=None, **options):
|
|
|
|
self.app_or_project = app_or_project
|
|
|
|
self.paths_to_remove = []
|
|
|
|
self.verbosity = int(options.get('verbosity'))
|
|
|
|
|
2012-10-13 22:05:34 +08:00
|
|
|
self.validate_name(name, app_or_project)
|
2011-12-30 03:06:57 +08:00
|
|
|
|
2011-12-23 06:38:02 +08:00
|
|
|
# if some directory is given, make sure it's nicely expanded
|
|
|
|
if target is None:
|
2012-01-05 07:55:34 +08:00
|
|
|
top_dir = path.join(os.getcwd(), name)
|
|
|
|
try:
|
|
|
|
os.makedirs(top_dir)
|
2012-04-29 00:09:37 +08:00
|
|
|
except OSError as e:
|
2012-01-16 00:06:56 +08:00
|
|
|
if e.errno == errno.EEXIST:
|
|
|
|
message = "'%s' already exists" % top_dir
|
|
|
|
else:
|
|
|
|
message = e
|
|
|
|
raise CommandError(message)
|
2011-12-23 06:38:02 +08:00
|
|
|
else:
|
2012-03-22 06:29:32 +08:00
|
|
|
top_dir = os.path.abspath(path.expanduser(target))
|
|
|
|
if not os.path.exists(top_dir):
|
|
|
|
raise CommandError("Destination directory '%s' does not "
|
|
|
|
"exist, please create it first." % top_dir)
|
2011-12-23 06:38:02 +08:00
|
|
|
|
|
|
|
extensions = tuple(
|
|
|
|
handle_extensions(options.get('extensions'), ignored=()))
|
2012-02-04 21:01:30 +08:00
|
|
|
extra_files = []
|
|
|
|
for file in options.get('files'):
|
|
|
|
extra_files.extend(map(lambda x: x.strip(), file.split(',')))
|
2011-12-23 06:38:02 +08:00
|
|
|
if self.verbosity >= 2:
|
|
|
|
self.stdout.write("Rendering %s template files with "
|
|
|
|
"extensions: %s\n" %
|
|
|
|
(app_or_project, ', '.join(extensions)))
|
2012-02-04 21:01:30 +08:00
|
|
|
self.stdout.write("Rendering %s template files with "
|
|
|
|
"filenames: %s\n" %
|
|
|
|
(app_or_project, ', '.join(extra_files)))
|
2011-12-23 06:38:02 +08:00
|
|
|
|
|
|
|
base_name = '%s_name' % app_or_project
|
|
|
|
base_subdir = '%s_template' % app_or_project
|
|
|
|
base_directory = '%s_directory' % app_or_project
|
2013-06-20 21:39:58 +08:00
|
|
|
if django.VERSION[-2] != 'final':
|
Simplified default project template.
Squashed commit of:
commit 508ec9144b35c50794708225b496bde1eb5e60aa
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 22:50:55 2013 +0100
Tweaked default settings file.
* Explained why BASE_DIR exists.
* Added a link to the database configuration options, and put it in its
own section.
* Moved sensitive settings that must be changed for production at the
top.
commit 6515fd2f1aa73a86dc8dbd2ccf512ddb6b140d57
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 14:35:21 2013 +0100
Documented the simplified app & project templates in the changelog.
commit 2c5b576c2ea91d84273a019b3d0b3b8b4da72f23
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 13:59:27 2013 +0100
Minor fixes in tutorials 5 and 6.
commit 55a51531be8104f21b3cca3f6bf70b0a7139a041
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 13:51:11 2013 +0100
Updated tutorial 2 for the new project template.
commit 29ddae87bdaecff12dd31b16b000c01efbde9e20
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 11:58:54 2013 +0100
Updated tutorial 1 for the new project template.
commit 0ecb9f6e2514cfd26a678a280d471433375101a3
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 11:29:13 2013 +0100
Adjusted the default URLconf detection to account for the admin.
It's now enabled by default.
commit 5fb4da0d3d09dac28dd94e3fde92b9d4335c0565
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 10:36:55 2013 +0100
Added security warnings for the most sensitive settings.
commit 718d84bd8ac4a42fb4b28ec93965de32680f091e
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 23:24:06 2013 +0100
Used an absolute path for the SQLite database.
This ensures the settings file works regardless of which directory
django-admin.py / manage.py is invoked from.
BASE_DIR got a +1 from a BDFL and another core dev. It doesn't involve
the concept of a "Django project"; it's just a convenient way to express
relative paths within the source code repository for non-Python files.
Thanks Jacob Kaplan-Moss for the suggestion.
commit 1b559b4bcda622e10909b68fe5cab90db6727dd9
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 23:22:40 2013 +0100
Removed STATIC_ROOT from the default settings template.
It isn't necessary in development, and it confuses beginners to no end.
Thanks Carl Meyer for the suggestion.
commit a55f141a500bb7c9a1bc259bbe1954c13b199671
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 23:21:43 2013 +0100
Removed MEDIA_ROOT/URL from default settings template.
Many sites will never deal with user-uploaded files, and MEDIA_ROOT is
complicated to explain.
Thanks Carl Meyer for the suggestion.
commit 44bf2f2441420fd9429ee9fe1f7207f92dd87e70
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 22:22:09 2013 +0100
Removed logging config.
This configuration is applied regardless of the value of LOGGING;
duplicating it in LOGGING is confusing.
commit eac747e848eaed65fd5f6f254f0a7559d856f88f
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 22:05:31 2013 +0100
Enabled the locale middleware by default.
USE_I18N is True by default, and doesn't work well without
LocaleMiddleware.
commit d806c62b2d00826dc2688c84b092627b8d571cab
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 22:03:16 2013 +0100
Enabled clickjacking protection by default.
commit 99152c30e6a15003f0b6737dc78e87adf462aacb
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 22:01:48 2013 +0100
Reorganized settings in logical sections, and trimmed comments.
commit d37ffdfcb24b7e0ec7cc113d07190f65fb12fb8a
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:54:11 2013 +0100
Avoided misleading TEMPLATE_DEBUG = DEBUG.
According to the docs TEMPLATE_DEBUG works only when DEBUG = True.
commit 15d9478d3a9850e85841e7cf09cf83050371c6bf
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:46:25 2013 +0100
Removed STATICFILES_FINDERS/TEMPLATE_LOADERS from default settings file.
Only developers with special needs ever need to change these settings.
commit 574da0eb5bfb4570883756914b4dbd7e20e1f61e
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:45:01 2013 +0100
Removed STATICFILES/TEMPLATES_DIRS from default settings file.
The current best practice is to put static files and templates in
applications, for easier testing and deployment.
commit 8cb18dbe56629aa1be74718a07e7cc66b4f9c9f0
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:24:16 2013 +0100
Removed settings related to email reporting from default settings file.
While handy for small scale projects, it isn't exactly a best practice.
commit 8ecbfcb3638058f0c49922540f874a7d802d864f
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 18:54:43 2013 +0100
Documented how to enable the sites framework.
commit 23fc91a6fa67d91ddd9d71b1c3e0dc26bdad9841
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:28:59 2013 +0100
Disabled the sites framework by default.
RequestSite does the job for single-domain websites.
commit c4d82eb8afc0eb8568bf9c4d12644272415e3960
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 00:08:33 2013 +0100
Added a default admin.py to the application template.
Thanks Ryan D Hiebert for the suggestion.
commit 4071dc771e5c44b1c5ebb9beecefb164ae465e22
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 10:59:49 2013 +0100
Enabled the admin by default.
Everyone uses the admin.
commit c807a31f8d89e7e7fd97380e3023f7983a8b6fcb
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 10:57:05 2013 +0100
Removed admindocs from default project template.
commit 09e4ce0e652a97da1a9e285046a91c8ad7a9189c
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:32:52 2013 +0100
Added links to the settings documentation.
commit 5b8f5eaef364eb790fcde6f9e86f7d266074cca8
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 11:06:54 2013 +0100
Used a significant example for URLconf includes.
commit 908e91d6fcee2a3cb51ca26ecdf12a6a24e69ef8
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:22:31 2013 +0100
Moved code comments about WSGI to docs, and rewrote said docs.
commit 50417e51996146f891d08ca8b74dcc736a581932
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 15:51:50 2013 +0100
Normalized the default application template.
Removed the default test that 1 + 1 = 2, because it's been committed
way too many times, in too many projects.
Added an import of `render` for views, because the first view will
often be:
def home(request):
return render(request, "mysite/home.html")
2013-01-28 22:51:50 +08:00
|
|
|
docs_version = 'dev'
|
|
|
|
else:
|
|
|
|
docs_version = '%d.%d' % django.VERSION[:2]
|
2011-12-23 06:38:02 +08:00
|
|
|
|
|
|
|
context = Context(dict(options, **{
|
|
|
|
base_name: name,
|
|
|
|
base_directory: top_dir,
|
Simplified default project template.
Squashed commit of:
commit 508ec9144b35c50794708225b496bde1eb5e60aa
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 22:50:55 2013 +0100
Tweaked default settings file.
* Explained why BASE_DIR exists.
* Added a link to the database configuration options, and put it in its
own section.
* Moved sensitive settings that must be changed for production at the
top.
commit 6515fd2f1aa73a86dc8dbd2ccf512ddb6b140d57
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 14:35:21 2013 +0100
Documented the simplified app & project templates in the changelog.
commit 2c5b576c2ea91d84273a019b3d0b3b8b4da72f23
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 13:59:27 2013 +0100
Minor fixes in tutorials 5 and 6.
commit 55a51531be8104f21b3cca3f6bf70b0a7139a041
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 13:51:11 2013 +0100
Updated tutorial 2 for the new project template.
commit 29ddae87bdaecff12dd31b16b000c01efbde9e20
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 11:58:54 2013 +0100
Updated tutorial 1 for the new project template.
commit 0ecb9f6e2514cfd26a678a280d471433375101a3
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 11:29:13 2013 +0100
Adjusted the default URLconf detection to account for the admin.
It's now enabled by default.
commit 5fb4da0d3d09dac28dd94e3fde92b9d4335c0565
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 10:36:55 2013 +0100
Added security warnings for the most sensitive settings.
commit 718d84bd8ac4a42fb4b28ec93965de32680f091e
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 23:24:06 2013 +0100
Used an absolute path for the SQLite database.
This ensures the settings file works regardless of which directory
django-admin.py / manage.py is invoked from.
BASE_DIR got a +1 from a BDFL and another core dev. It doesn't involve
the concept of a "Django project"; it's just a convenient way to express
relative paths within the source code repository for non-Python files.
Thanks Jacob Kaplan-Moss for the suggestion.
commit 1b559b4bcda622e10909b68fe5cab90db6727dd9
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 23:22:40 2013 +0100
Removed STATIC_ROOT from the default settings template.
It isn't necessary in development, and it confuses beginners to no end.
Thanks Carl Meyer for the suggestion.
commit a55f141a500bb7c9a1bc259bbe1954c13b199671
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 23:21:43 2013 +0100
Removed MEDIA_ROOT/URL from default settings template.
Many sites will never deal with user-uploaded files, and MEDIA_ROOT is
complicated to explain.
Thanks Carl Meyer for the suggestion.
commit 44bf2f2441420fd9429ee9fe1f7207f92dd87e70
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 22:22:09 2013 +0100
Removed logging config.
This configuration is applied regardless of the value of LOGGING;
duplicating it in LOGGING is confusing.
commit eac747e848eaed65fd5f6f254f0a7559d856f88f
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 22:05:31 2013 +0100
Enabled the locale middleware by default.
USE_I18N is True by default, and doesn't work well without
LocaleMiddleware.
commit d806c62b2d00826dc2688c84b092627b8d571cab
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 22:03:16 2013 +0100
Enabled clickjacking protection by default.
commit 99152c30e6a15003f0b6737dc78e87adf462aacb
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 22:01:48 2013 +0100
Reorganized settings in logical sections, and trimmed comments.
commit d37ffdfcb24b7e0ec7cc113d07190f65fb12fb8a
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:54:11 2013 +0100
Avoided misleading TEMPLATE_DEBUG = DEBUG.
According to the docs TEMPLATE_DEBUG works only when DEBUG = True.
commit 15d9478d3a9850e85841e7cf09cf83050371c6bf
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:46:25 2013 +0100
Removed STATICFILES_FINDERS/TEMPLATE_LOADERS from default settings file.
Only developers with special needs ever need to change these settings.
commit 574da0eb5bfb4570883756914b4dbd7e20e1f61e
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:45:01 2013 +0100
Removed STATICFILES/TEMPLATES_DIRS from default settings file.
The current best practice is to put static files and templates in
applications, for easier testing and deployment.
commit 8cb18dbe56629aa1be74718a07e7cc66b4f9c9f0
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:24:16 2013 +0100
Removed settings related to email reporting from default settings file.
While handy for small scale projects, it isn't exactly a best practice.
commit 8ecbfcb3638058f0c49922540f874a7d802d864f
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 18:54:43 2013 +0100
Documented how to enable the sites framework.
commit 23fc91a6fa67d91ddd9d71b1c3e0dc26bdad9841
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:28:59 2013 +0100
Disabled the sites framework by default.
RequestSite does the job for single-domain websites.
commit c4d82eb8afc0eb8568bf9c4d12644272415e3960
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue Jan 29 00:08:33 2013 +0100
Added a default admin.py to the application template.
Thanks Ryan D Hiebert for the suggestion.
commit 4071dc771e5c44b1c5ebb9beecefb164ae465e22
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 10:59:49 2013 +0100
Enabled the admin by default.
Everyone uses the admin.
commit c807a31f8d89e7e7fd97380e3023f7983a8b6fcb
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 10:57:05 2013 +0100
Removed admindocs from default project template.
commit 09e4ce0e652a97da1a9e285046a91c8ad7a9189c
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:32:52 2013 +0100
Added links to the settings documentation.
commit 5b8f5eaef364eb790fcde6f9e86f7d266074cca8
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 11:06:54 2013 +0100
Used a significant example for URLconf includes.
commit 908e91d6fcee2a3cb51ca26ecdf12a6a24e69ef8
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 16:22:31 2013 +0100
Moved code comments about WSGI to docs, and rewrote said docs.
commit 50417e51996146f891d08ca8b74dcc736a581932
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon Jan 28 15:51:50 2013 +0100
Normalized the default application template.
Removed the default test that 1 + 1 = 2, because it's been committed
way too many times, in too many projects.
Added an import of `render` for views, because the first view will
often be:
def home(request):
return render(request, "mysite/home.html")
2013-01-28 22:51:50 +08:00
|
|
|
'docs_version': docs_version,
|
2012-07-17 06:26:31 +08:00
|
|
|
}), autoescape=False)
|
2011-12-23 06:38:02 +08:00
|
|
|
|
|
|
|
# Setup a stub settings environment for template rendering
|
|
|
|
from django.conf import settings
|
|
|
|
if not settings.configured:
|
|
|
|
settings.configure()
|
|
|
|
|
|
|
|
template_dir = self.handle_template(options.get('template'),
|
|
|
|
base_subdir)
|
|
|
|
prefix_length = len(template_dir) + 1
|
|
|
|
|
|
|
|
for root, dirs, files in os.walk(template_dir):
|
|
|
|
|
|
|
|
path_rest = root[prefix_length:]
|
|
|
|
relative_dir = path_rest.replace(base_name, name)
|
|
|
|
if relative_dir:
|
|
|
|
target_dir = path.join(top_dir, relative_dir)
|
|
|
|
if not path.exists(target_dir):
|
|
|
|
os.mkdir(target_dir)
|
|
|
|
|
|
|
|
for dirname in dirs[:]:
|
2012-10-07 05:40:58 +08:00
|
|
|
if dirname.startswith('.') or dirname == '__pycache__':
|
2011-12-23 06:38:02 +08:00
|
|
|
dirs.remove(dirname)
|
|
|
|
|
|
|
|
for filename in files:
|
|
|
|
if filename.endswith(('.pyo', '.pyc', '.py.class')):
|
|
|
|
# Ignore some files as they cause various breakages.
|
|
|
|
continue
|
|
|
|
old_path = path.join(root, filename)
|
|
|
|
new_path = path.join(top_dir, relative_dir,
|
|
|
|
filename.replace(base_name, name))
|
|
|
|
if path.exists(new_path):
|
|
|
|
raise CommandError("%s already exists, overlaying a "
|
|
|
|
"project or app into an existing "
|
|
|
|
"directory won't replace conflicting "
|
|
|
|
"files" % new_path)
|
|
|
|
|
|
|
|
# Only render the Python files, as we don't want to
|
|
|
|
# accidentally render Django templates files
|
2012-12-03 17:37:26 +08:00
|
|
|
with open(old_path, 'rb') as template_file:
|
2011-12-23 06:38:02 +08:00
|
|
|
content = template_file.read()
|
2012-02-04 21:01:30 +08:00
|
|
|
if filename.endswith(extensions) or filename in extra_files:
|
2012-12-03 17:37:26 +08:00
|
|
|
content = content.decode('utf-8')
|
2011-12-23 06:38:02 +08:00
|
|
|
template = Template(content)
|
|
|
|
content = template.render(context)
|
2012-12-03 17:37:26 +08:00
|
|
|
content = content.encode('utf-8')
|
|
|
|
with open(new_path, 'wb') as new_file:
|
2011-12-23 06:38:02 +08:00
|
|
|
new_file.write(content)
|
|
|
|
|
|
|
|
if self.verbosity >= 2:
|
|
|
|
self.stdout.write("Creating %s\n" % new_path)
|
|
|
|
try:
|
|
|
|
shutil.copymode(old_path, new_path)
|
|
|
|
self.make_writeable(new_path)
|
|
|
|
except OSError:
|
2012-05-19 19:51:54 +08:00
|
|
|
self.stderr.write(
|
2011-12-23 06:38:02 +08:00
|
|
|
"Notice: Couldn't set permission bits on %s. You're "
|
|
|
|
"probably using an uncommon filesystem setup. No "
|
2012-05-19 19:51:54 +08:00
|
|
|
"problem." % new_path, self.style.NOTICE)
|
2011-12-23 06:38:02 +08:00
|
|
|
|
|
|
|
if self.paths_to_remove:
|
|
|
|
if self.verbosity >= 2:
|
|
|
|
self.stdout.write("Cleaning up temporary files.\n")
|
|
|
|
for path_to_remove in self.paths_to_remove:
|
2011-12-30 01:03:38 +08:00
|
|
|
if path.isfile(path_to_remove):
|
2011-12-23 06:38:02 +08:00
|
|
|
os.remove(path_to_remove)
|
|
|
|
else:
|
|
|
|
shutil.rmtree(path_to_remove,
|
|
|
|
onerror=rmtree_errorhandler)
|
|
|
|
|
|
|
|
def handle_template(self, template, subdir):
|
|
|
|
"""
|
|
|
|
Determines where the app or project templates are.
|
|
|
|
Use django.__path__[0] as the default because we don't
|
|
|
|
know into which directory Django has been installed.
|
|
|
|
"""
|
|
|
|
if template is None:
|
|
|
|
return path.join(django.__path__[0], 'conf', subdir)
|
|
|
|
else:
|
|
|
|
if template.startswith('file://'):
|
|
|
|
template = template[7:]
|
|
|
|
expanded_template = path.expanduser(template)
|
2011-12-30 01:03:38 +08:00
|
|
|
expanded_template = path.normpath(expanded_template)
|
|
|
|
if path.isdir(expanded_template):
|
2011-12-23 06:38:02 +08:00
|
|
|
return expanded_template
|
|
|
|
if self.is_url(template):
|
|
|
|
# downloads the file and returns the path
|
|
|
|
absolute_path = self.download(template)
|
|
|
|
else:
|
|
|
|
absolute_path = path.abspath(expanded_template)
|
2011-12-30 01:03:38 +08:00
|
|
|
if path.exists(absolute_path):
|
2011-12-23 06:38:02 +08:00
|
|
|
return self.extract(absolute_path)
|
|
|
|
|
|
|
|
raise CommandError("couldn't handle %s template %s." %
|
|
|
|
(self.app_or_project, template))
|
|
|
|
|
2012-10-13 22:05:34 +08:00
|
|
|
def validate_name(self, name, app_or_project):
|
|
|
|
if name is None:
|
|
|
|
raise CommandError("you must provide %s %s name" % (
|
|
|
|
"an" if app_or_project == "app" else "a", app_or_project))
|
|
|
|
# If it's not a valid directory name.
|
|
|
|
if not re.search(r'^[_a-zA-Z]\w*$', name):
|
|
|
|
# Provide a smart error message, depending on the error.
|
|
|
|
if not re.search(r'^[_a-zA-Z]', name):
|
|
|
|
message = 'make sure the name begins with a letter or underscore'
|
|
|
|
else:
|
|
|
|
message = 'use only numbers, letters and underscores'
|
|
|
|
raise CommandError("%r is not a valid %s name. Please %s." %
|
|
|
|
(name, app_or_project, message))
|
|
|
|
|
2011-12-23 06:38:02 +08:00
|
|
|
def download(self, url):
|
|
|
|
"""
|
|
|
|
Downloads the given URL and returns the file name.
|
|
|
|
"""
|
2012-01-02 01:45:40 +08:00
|
|
|
def cleanup_url(url):
|
|
|
|
tmp = url.rstrip('/')
|
|
|
|
filename = tmp.split('/')[-1]
|
|
|
|
if url.endswith('/'):
|
2013-10-22 21:31:43 +08:00
|
|
|
display_url = tmp + '/'
|
2012-01-02 01:45:40 +08:00
|
|
|
else:
|
|
|
|
display_url = url
|
|
|
|
return filename, display_url
|
|
|
|
|
2011-12-23 06:38:02 +08:00
|
|
|
prefix = 'django_%s_template_' % self.app_or_project
|
|
|
|
tempdir = tempfile.mkdtemp(prefix=prefix, suffix='_download')
|
|
|
|
self.paths_to_remove.append(tempdir)
|
2012-01-02 01:45:40 +08:00
|
|
|
filename, display_url = cleanup_url(url)
|
2011-12-23 06:38:02 +08:00
|
|
|
|
|
|
|
if self.verbosity >= 2:
|
2012-01-02 01:45:40 +08:00
|
|
|
self.stdout.write("Downloading %s\n" % display_url)
|
2011-12-23 06:38:02 +08:00
|
|
|
try:
|
2012-07-20 21:36:52 +08:00
|
|
|
the_path, info = urlretrieve(url, path.join(tempdir, filename))
|
2012-04-29 00:09:37 +08:00
|
|
|
except IOError as e:
|
2011-12-23 06:38:02 +08:00
|
|
|
raise CommandError("couldn't download URL %s to %s: %s" %
|
|
|
|
(url, filename, e))
|
|
|
|
|
2011-12-30 01:03:38 +08:00
|
|
|
used_name = the_path.split('/')[-1]
|
2011-12-23 06:38:02 +08:00
|
|
|
|
|
|
|
# Trying to get better name from response headers
|
|
|
|
content_disposition = info.get('content-disposition')
|
|
|
|
if content_disposition:
|
|
|
|
_, params = cgi.parse_header(content_disposition)
|
|
|
|
guessed_filename = params.get('filename') or used_name
|
|
|
|
else:
|
|
|
|
guessed_filename = used_name
|
|
|
|
|
|
|
|
# Falling back to content type guessing
|
|
|
|
ext = self.splitext(guessed_filename)[1]
|
|
|
|
content_type = info.get('content-type')
|
|
|
|
if not ext and content_type:
|
|
|
|
ext = mimetypes.guess_extension(content_type)
|
|
|
|
if ext:
|
|
|
|
guessed_filename += ext
|
|
|
|
|
|
|
|
# Move the temporary file to a filename that has better
|
|
|
|
# chances of being recognnized by the archive utils
|
|
|
|
if used_name != guessed_filename:
|
2011-12-30 01:03:38 +08:00
|
|
|
guessed_path = path.join(tempdir, guessed_filename)
|
|
|
|
shutil.move(the_path, guessed_path)
|
2011-12-23 06:38:02 +08:00
|
|
|
return guessed_path
|
|
|
|
|
|
|
|
# Giving up
|
2011-12-30 01:03:38 +08:00
|
|
|
return the_path
|
2011-12-23 06:38:02 +08:00
|
|
|
|
2011-12-30 01:03:38 +08:00
|
|
|
def splitext(self, the_path):
|
2011-12-23 06:38:02 +08:00
|
|
|
"""
|
|
|
|
Like os.path.splitext, but takes off .tar, too
|
|
|
|
"""
|
2011-12-30 01:03:38 +08:00
|
|
|
base, ext = posixpath.splitext(the_path)
|
2011-12-23 06:38:02 +08:00
|
|
|
if base.lower().endswith('.tar'):
|
|
|
|
ext = base[-4:] + ext
|
|
|
|
base = base[:-4]
|
|
|
|
return base, ext
|
|
|
|
|
|
|
|
def extract(self, filename):
|
|
|
|
"""
|
|
|
|
Extracts the given file to a temporarily and returns
|
|
|
|
the path of the directory with the extracted content.
|
|
|
|
"""
|
|
|
|
prefix = 'django_%s_template_' % self.app_or_project
|
|
|
|
tempdir = tempfile.mkdtemp(prefix=prefix, suffix='_extract')
|
|
|
|
self.paths_to_remove.append(tempdir)
|
|
|
|
if self.verbosity >= 2:
|
|
|
|
self.stdout.write("Extracting %s\n" % filename)
|
|
|
|
try:
|
|
|
|
archive.extract(filename, tempdir)
|
|
|
|
return tempdir
|
2012-04-29 00:09:37 +08:00
|
|
|
except (archive.ArchiveException, IOError) as e:
|
2011-12-23 06:38:02 +08:00
|
|
|
raise CommandError("couldn't extract file %s to %s: %s" %
|
|
|
|
(filename, tempdir, e))
|
|
|
|
|
|
|
|
def is_url(self, template):
|
|
|
|
"""
|
|
|
|
Returns True if the name looks like a URL
|
|
|
|
"""
|
|
|
|
if ':' not in template:
|
|
|
|
return False
|
|
|
|
scheme = template.split(':', 1)[0].lower()
|
|
|
|
return scheme in self.url_schemes
|
|
|
|
|
|
|
|
def make_writeable(self, filename):
|
|
|
|
"""
|
|
|
|
Make sure that the file is writeable.
|
|
|
|
Useful if our source is read-only.
|
|
|
|
"""
|
|
|
|
if sys.platform.startswith('java'):
|
|
|
|
# On Jython there is no os.access()
|
|
|
|
return
|
|
|
|
if not os.access(filename, os.W_OK):
|
|
|
|
st = os.stat(filename)
|
|
|
|
new_permissions = stat.S_IMODE(st.st_mode) | stat.S_IWUSR
|
|
|
|
os.chmod(filename, new_permissions)
|