diff --git a/django/conf/project_template/project_name/settings.py b/django/conf/project_template/project_name/settings.py index f28c61e15d5..a5a25b95c97 100644 --- a/django/conf/project_template/project_name/settings.py +++ b/django/conf/project_template/project_name/settings.py @@ -81,7 +81,7 @@ STATICFILES_FINDERS = ( ) # Make this unique, and don't share it with anybody. -SECRET_KEY = '' +SECRET_KEY = '{{ secret_key }}' # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ( diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 7e8eebdaa3d..367263955c2 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -77,9 +77,7 @@ def get_commands(): in that package are registered. Core commands are always included. If a settings module has been - specified, user-defined commands will also be included, the - startproject command will be disabled, and the startapp command - will be modified to use the directory in which the settings module appears. + specified, user-defined commands will also be included. The dictionary is in the format {command_name: app_name}. Key-value pairs from this dictionary can then be used in calls to diff --git a/django/core/management/base.py b/django/core/management/base.py index 23ff7d6194e..db855e12941 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -3,9 +3,10 @@ Base classes for writing management commands (named commands which can be executed through ``django-admin.py`` or ``manage.py``). """ - +from __future__ import with_statement import os import sys + from optparse import make_option, OptionParser import traceback @@ -14,6 +15,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.management.color import color_style from django.utils.encoding import smart_str + class CommandError(Exception): """ Exception class indicating a problem while executing a management @@ -29,6 +31,7 @@ class CommandError(Exception): """ pass + def handle_default_options(options): """ Include any default options that all commands should accept here @@ -41,6 +44,7 @@ def handle_default_options(options): if options.pythonpath: sys.path.insert(0, options.pythonpath) + class BaseCommand(object): """ The base class from which all management commands ultimately @@ -134,7 +138,7 @@ class BaseCommand(object): # Configuration shortcuts that alter various logic. can_import_settings = True requires_model_validation = True - output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;" + output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;" def __init__(self): self.style = color_style() @@ -275,6 +279,7 @@ class BaseCommand(object): """ raise NotImplementedError() + class AppCommand(BaseCommand): """ A management command which takes one or more installed application @@ -310,6 +315,7 @@ class AppCommand(BaseCommand): """ raise NotImplementedError() + class LabelCommand(BaseCommand): """ A management command which takes one or more arbitrary arguments @@ -345,6 +351,7 @@ class LabelCommand(BaseCommand): """ raise NotImplementedError() + class NoArgsCommand(BaseCommand): """ A command which takes no arguments on the command line. @@ -369,74 +376,3 @@ class NoArgsCommand(BaseCommand): """ raise NotImplementedError() - -def copy_helper(style, app_or_project, name, directory): - """ - Copies either a Django application layout template or a Django project - layout template into the specified directory. - - """ - # style -- A color style object (see django.core.management.color). - # app_or_project -- The string 'app' or 'project'. - # name -- The name of the application or project. - # directory -- The directory to which the layout template should be copied. - import re - import shutil - - if not re.search(r'^[_a-zA-Z]\w*$', name): # If it's not a valid directory 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)) - top_dir = os.path.join(directory, name) - try: - os.mkdir(top_dir) - except OSError, e: - raise CommandError(e) - - # Determine where the app or project templates are. Use - # django.__path__[0] because we don't know into which directory - # django has been installed. - template_dir = os.path.join(django.__path__[0], 'conf', '%s_template' % app_or_project) - - for d, subdirs, files in os.walk(template_dir): - relative_dir = d[len(template_dir)+1:].replace('%s_name' % app_or_project, name) - if relative_dir: - os.mkdir(os.path.join(top_dir, relative_dir)) - for subdir in subdirs[:]: - if subdir.startswith('.'): - subdirs.remove(subdir) - for f in files: - if not f.endswith('.py'): - # Ignore .pyc, .pyo, .py.class etc, as they cause various - # breakages. - continue - path_old = os.path.join(d, f) - path_new = os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name)) - fp_old = open(path_old, 'r') - fp_new = open(path_new, 'w') - fp_new.write(fp_old.read().replace('{{ %s_name }}' % app_or_project, name)) - fp_old.close() - fp_new.close() - try: - shutil.copymode(path_old, path_new) - _make_writeable(path_new) - except OSError: - sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new)) - -def _make_writeable(filename): - """ - Make sure that the file is writeable. Useful if our source is - read-only. - - """ - import stat - 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) diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py index e31c6c8d316..7cd2a9ad0f5 100644 --- a/django/core/management/commands/makemessages.py +++ b/django/core/management/commands/makemessages.py @@ -13,7 +13,7 @@ from django.utils.jslex import prepare_js_for_gettext plural_forms_re = re.compile(r'^(?P"Plural-Forms.+?\\n")\s*$', re.MULTILINE | re.DOTALL) -def handle_extensions(extensions=('html',)): +def handle_extensions(extensions=('html',), ignored=('py',)): """ Organizes multiple extensions that are separated with commas or passed by using --extension/-e multiple times. Note that the .py extension is ignored @@ -31,11 +31,11 @@ def handle_extensions(extensions=('html',)): """ ext_list = [] for ext in extensions: - ext_list.extend(ext.replace(' ','').split(',')) + ext_list.extend(ext.replace(' ', '').split(',')) for i, ext in enumerate(ext_list): if not ext.startswith('.'): ext_list[i] = '.%s' % ext_list[i] - return set([x for x in ext_list if x != '.py']) + return set([x for x in ext_list if x.strip('.') not in ignored]) def _popen(cmd): """ diff --git a/django/core/management/commands/startapp.py b/django/core/management/commands/startapp.py index 2999a33ed48..5581331b7b3 100644 --- a/django/core/management/commands/startapp.py +++ b/django/core/management/commands/startapp.py @@ -1,21 +1,16 @@ -import os - -from django.core.management.base import copy_helper, CommandError, LabelCommand +from django.core.management.base import CommandError +from django.core.management.templates import TemplateCommand from django.utils.importlib import import_module -class Command(LabelCommand): - help = "Creates a Django app directory structure for the given app name in the current directory." - args = "[appname]" - label = 'application name' - requires_model_validation = False - # Can't import settings during this command, because they haven't - # necessarily been created. - can_import_settings = False +class Command(TemplateCommand): + help = ("Creates a Django app directory structure for the given app " + "name in the current directory or optionally in the given " + "directory.") - def handle_label(self, app_name, directory=None, **options): - if directory is None: - directory = os.getcwd() + def handle(self, app_name=None, target=None, **options): + if app_name is None: + raise CommandError("you must provide an app name") # Check that the app_name cannot be imported. try: @@ -23,6 +18,8 @@ class Command(LabelCommand): except ImportError: pass else: - raise CommandError("%r conflicts with the name of an existing Python module and cannot be used as an app name. Please try another name." % app_name) + raise CommandError("%r conflicts with the name of an existing " + "Python module and cannot be used as an app " + "name. Please try another name." % app_name) - copy_helper(self.style, 'app', app_name, directory) + super(Command, self).handle('app', app_name, target, **options) diff --git a/django/core/management/commands/startproject.py b/django/core/management/commands/startproject.py index ce4d32bae87..c849ab69c37 100644 --- a/django/core/management/commands/startproject.py +++ b/django/core/management/commands/startproject.py @@ -1,21 +1,18 @@ -from django.core.management.base import copy_helper, CommandError, LabelCommand -from django.utils.importlib import import_module -import os -import re from random import choice -class Command(LabelCommand): - help = "Creates a Django project directory structure for the given project name in the current directory." - args = "[projectname]" - label = 'project name' +from django.core.management.base import CommandError +from django.core.management.templates import TemplateCommand +from django.utils.importlib import import_module - requires_model_validation = False - # Can't import settings during this command, because they haven't - # necessarily been created. - can_import_settings = False - def handle_label(self, project_name, **options): - directory = os.getcwd() +class Command(TemplateCommand): + help = ("Creates a Django project directory structure for the given " + "project name in the current directory or optionally in the " + "given directory.") + + def handle(self, project_name=None, target=None, *args, **options): + if project_name is None: + raise CommandError("you must provide a project name") # Check that the project_name cannot be imported. try: @@ -23,15 +20,13 @@ class Command(LabelCommand): except ImportError: pass else: - raise CommandError("%r conflicts with the name of an existing Python module and cannot be used as a project name. Please try another name." % project_name) + raise CommandError("%r conflicts with the name of an existing " + "Python module and cannot be used as a " + "project name. Please try another name." % + project_name) - copy_helper(self.style, 'project', project_name, directory) + # Create a random SECRET_KEY hash to put it in the main settings. + chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' + options['secret_key'] = ''.join([choice(chars) for i in range(50)]) - # Create a random SECRET_KEY hash, and put it in the main settings. - main_settings_file = os.path.join(directory, project_name, project_name, 'settings.py') - settings_contents = open(main_settings_file, 'r').read() - fp = open(main_settings_file, 'w') - secret_key = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]) - settings_contents = re.sub(r"(?<=SECRET_KEY = ')'", secret_key + "'", settings_contents) - fp.write(settings_contents) - fp.close() + super(Command, self).handle('project', project_name, target, **options) diff --git a/django/core/management/templates.py b/django/core/management/templates.py new file mode 100644 index 00000000000..cf6801d18bc --- /dev/null +++ b/django/core/management/templates.py @@ -0,0 +1,287 @@ +from __future__ import with_statement +import cgi +import mimetypes +import os +import posixpath +import re +import shutil +import stat +import sys +import tempfile +import urllib + +from optparse import make_option +from os import path + +import django +from django.template import Template, Context +from django.utils import archive +from django.utils.encoding import smart_str +from django.utils._os import rmtree_errorhandler +from django.core.management.base import BaseCommand, CommandError +from django.core.management.commands.makemessages import handle_extensions + + +_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', + help='The dotted import path to load the template from.'), + make_option('--extension', '-e', dest='extensions', + action='append', default=['py'], + help='The file extension(s) to render (default: "py") ' + 'Separate multiple extensions with commas, or use ' + '-e multiple times.'), + ) + 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'] + + 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')) + + # if some directory is given, make sure it's nicely expanded + if target is None: + target = os.getcwd() + else: + target = path.expanduser(target) + + top_dir = path.join(target, name) + try: + os.makedirs(top_dir) + except OSError, e: + raise CommandError(e) + + extensions = tuple( + handle_extensions(options.get('extensions'), ignored=())) + if self.verbosity >= 2: + self.stdout.write("Rendering %s template files with " + "extensions: %s\n" % + (app_or_project, ', '.join(extensions))) + + base_name = '%s_name' % app_or_project + base_subdir = '%s_template' % app_or_project + base_directory = '%s_directory' % app_or_project + + context = Context(dict(options, **{ + base_name: name, + base_directory: top_dir, + })) + + # 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)) + + # 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[:]: + if dirname.startswith('.'): + 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 + with open(old_path, 'r') as template_file: + content = template_file.read() + if filename.endswith(extensions): + template = Template(content) + content = template.render(context) + with open(new_path, 'w') as new_file: + 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: + notice = self.style.NOTICE( + "Notice: Couldn't set permission bits on %s. You're " + "probably using an uncommon filesystem setup. No " + "problem.\n" % new_path) + sys.stderr.write(smart_str(notice)) + + 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: + if os.path.isfile(path_to_remove): + 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) + if os.path.isdir(expanded_template): + 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) + if os.path.exists(absolute_path): + return self.extract(absolute_path) + + raise CommandError("couldn't handle %s template %s." % + (self.app_or_project, template)) + + def download(self, url): + """ + Downloads the given URL and returns the file name. + """ + prefix = 'django_%s_template_' % self.app_or_project + tempdir = tempfile.mkdtemp(prefix=prefix, suffix='_download') + self.paths_to_remove.append(tempdir) + filename = url.split('/')[-1] + + if self.verbosity >= 2: + self.stdout.write("Downloading %s\n" % url) + try: + path, info = urllib.urlretrieve(url, + os.path.join(tempdir, filename)) + except IOError, e: + raise CommandError("couldn't download URL %s to %s: %s" % + (url, filename, e)) + + used_name = path.split('/')[-1] + + # 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: + guessed_path = os.path.join(tempdir, guessed_filename) + shutil.move(path, guessed_path) + return guessed_path + + # Giving up + return path + + def splitext(self, path): + """ + Like os.path.splitext, but takes off .tar, too + """ + base, ext = posixpath.splitext(path) + 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 + except (archive.ArchiveException, IOError), e: + 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) diff --git a/django/utils/archive.py b/django/utils/archive.py new file mode 100644 index 00000000000..8909cb6db73 --- /dev/null +++ b/django/utils/archive.py @@ -0,0 +1,198 @@ +""" +Based on "python-archive" -- http://pypi.python.org/pypi/python-archive/ + +Copyright (c) 2010 Gary Wilson Jr. and contributers. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" +import os +import shutil +import sys +import tarfile +import zipfile + + +class ArchiveException(Exception): + """ + Base exception class for all archive errors. + """ + + +class UnrecognizedArchiveFormat(ArchiveException): + """ + Error raised when passed file is not a recognized archive format. + """ + + +def extract(path, to_path=''): + """ + Unpack the tar or zip file at the specified path to the directory + specified by to_path. + """ + Archive(path).extract(to_path) + + +class Archive(object): + """ + The external API class that encapsulates an archive implementation. + """ + def __init__(self, file): + self._archive = self._archive_cls(file)(file) + + @staticmethod + def _archive_cls(file): + cls = None + if isinstance(file, basestring): + filename = file + else: + try: + filename = file.name + except AttributeError: + raise UnrecognizedArchiveFormat( + "File object not a recognized archive format.") + base, tail_ext = os.path.splitext(filename.lower()) + cls = extension_map.get(tail_ext) + if not cls: + base, ext = os.path.splitext(base) + cls = extension_map.get(ext) + if not cls: + raise UnrecognizedArchiveFormat( + "Path not a recognized archive format: %s" % filename) + return cls + + def extract(self, to_path=''): + self._archive.extract(to_path) + + def list(self): + self._archive.list() + + +class BaseArchive(object): + """ + Base Archive class. Implementations should inherit this class. + """ + def split_leading_dir(self, path): + path = str(path) + path = path.lstrip('/').lstrip('\\') + if '/' in path and (('\\' in path and path.find('/') < path.find('\\')) + or '\\' not in path): + return path.split('/', 1) + elif '\\' in path: + return path.split('\\', 1) + else: + return path, '' + + def has_leading_dir(self, paths): + """ + Returns true if all the paths have the same leading path name + (i.e., everything is in one subdirectory in an archive) + """ + common_prefix = None + for path in paths: + prefix, rest = self.split_leading_dir(path) + if not prefix: + return False + elif common_prefix is None: + common_prefix = prefix + elif prefix != common_prefix: + return False + return True + + def extract(self): + raise NotImplementedError + + def list(self): + raise NotImplementedError + + +class TarArchive(BaseArchive): + + def __init__(self, file): + self._archive = tarfile.open(file) + + def list(self, *args, **kwargs): + self._archive.list(*args, **kwargs) + + def extract(self, to_path): + # note: python<=2.5 doesnt seem to know about pax headers, filter them + members = [member for member in self._archive.getmembers() + if member.name != 'pax_global_header'] + leading = self.has_leading_dir(members) + for member in members: + name = member.name + if leading: + name = self.split_leading_dir(name)[1] + filename = os.path.join(to_path, name) + if member.isdir(): + if not os.path.exists(filename): + os.makedirs(filename) + else: + try: + extracted = self._archive.extractfile(member) + except (KeyError, AttributeError): + # Some corrupt tar files seem to produce this + # (specifically bad symlinks) + print ("In the tar file %s the member %s is invalid: %s" % + (name, member.name, sys.exc_info()[1])) + else: + dirname = os.path.dirname(filename) + if not os.path.exists(dirname): + os.makedirs(dirname) + with open(filename, 'wb') as outfile: + shutil.copyfileobj(extracted, outfile) + finally: + if extracted: + extracted.close() + + +class ZipArchive(BaseArchive): + + def __init__(self, file): + self._archive = zipfile.ZipFile(file) + + def list(self, *args, **kwargs): + self._archive.printdir(*args, **kwargs) + + def extract(self, to_path): + namelist = self._archive.namelist() + leading = self.has_leading_dir(namelist) + for name in namelist: + data = self._archive.read(name) + if leading: + name = self.split_leading_dir(name)[1] + filename = os.path.join(to_path, name) + dirname = os.path.dirname(filename) + if dirname and not os.path.exists(dirname): + os.makedirs(dirname) + if filename.endswith(('/', '\\')): + # A directory + if not os.path.exists(filename): + os.makedirs(filename) + else: + with open(filename, 'wb') as outfile: + outfile.write(data) + +extension_map = { + '.tar': TarArchive, + '.tar.bz2': TarArchive, + '.tar.gz': TarArchive, + '.tgz': TarArchive, + '.tz2': TarArchive, + '.zip': ZipArchive, +} diff --git a/docs/man/django-admin.1 b/docs/man/django-admin.1 index 62e45ae50e9..2326e827cf9 100644 --- a/docs/man/django-admin.1 +++ b/docs/man/django-admin.1 @@ -99,8 +99,7 @@ Prints the DROP TABLE SQL statements for the given app name(s). Prints the custom SQL statements for the given app name(s). .TP .BI "sqlflush [" "appname ..." "]" -Prints the SQL statements that would be executed for the "flush" -command. +Prints the SQL statements that would be executed for the "flush" command. .TP .BI "sqlindexes [" "appname ..." "]" Prints the CREATE INDEX SQL statements for the given model module name(s). @@ -116,13 +115,13 @@ name(s). Prints the SQL statements for resetting PostgreSQL sequences for the given app name(s). .TP -.BI "startapp [" "appname" "]" +.BI "startapp [" "\-\-template=PATH_OR_URL" "] [" "\-\-extension=EXTENSION" "] [" "appname" "] [" "destination" "]" Creates a Django app directory structure for the given app name in -the current directory. +the current directory or the optional destination. .TP -.BI "startproject [" "projectname" "]" +.BI "startproject [" "\-\-template=PATH_OR_URL" "] [" "\-\-extension=EXTENSION" "] [" "projectname" "] [" "destination" "]" Creates a Django project directory structure for the given project name -in the current directory. +in the current directory or the optional destination. .TP .BI syncdb Creates the database tables for all apps in INSTALLED_APPS whose tables @@ -194,7 +193,7 @@ The locale to process when using makemessages or compilemessages. The domain of the message files (default: "django") when using makemessages. .TP .I \-e, \-\-extension=EXTENSION -The file extension(s) to examine (default: ".html", separate multiple +The file extension(s) to examine (separate multiple extensions with commas, or use -e multiple times). .TP .I \-s, \-\-symlinks @@ -214,6 +213,9 @@ Don't break long message lines into several lines. .I \-a, \-\-all Process all available locales when using makemessages..SH "ENVIRONMENT" .TP +.I \-a, \-\-template=PATH_OR_URL +The file or directory path or URL to load the project and app templates from. +.TP .I DJANGO_SETTINGS_MODULE In the absence of the .BI \-\-settings diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index fac18f3b483..d33b18280a8 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -907,21 +907,121 @@ of sync with its automatically incremented field data. The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. -startapp ------------------- +startapp [destination] +-------------------------------- .. django-admin:: startapp Creates a Django app directory structure for the given app name in the current +directory or the given destination. + +.. versionchanged:: 1.4 + +By default the directory created contains a ``models.py`` file and other app +template files, see the `source`_ for more details. If only the app +name is given, the app directory will be created in the current working directory. -startproject --------------------------- +If the optional destination is provided, it will be used to create the +the new app directory in. The use of '.' to denote the current working +directory is valid for the destination. For example:: + + django-admin.py startapp myapp /Users/jezdez/Code + + +.. versionadded:: 1.4 +.. django-admin-option:: --template + +With the ``--template`` option you can use a custom app template by providing +either the path to a directory with the app template file, a path to a +compressed file (``.tar.gz``, ``.tar.bz2``, ``.tgz``, ``.tbz``, ``.zip``) +containing the app template files. + +Additionally Django will also accept URLs (``http``, ``https``, ``ftp``) to +compressed archives with the app template files, downloading and extracting +them on the fly. + +For example, this would look for an app template in the given directory when creating the ``myapp`` app:: + + django-admin.py startapp --template=/Users/jezdez/Code/my_app_template myapp + +.. versionadded:: 1.4 + +When Django copies the app template files it will also render the files +whose extension matches those passed with the ``--extension`` option (``py`` +by default) using the template engine. The :class:`template context +` used is: + +- Any option passed to the startapp command +- ``app_name`` -- the appp name as passed to the command +- ``app_directory`` -- the full path of the newly created app + +.. _render_warning: + +.. warning:: + + When the app template files are rendered with the Django template + engine (by default all ``*.py`` files) it will also replace all + stray template variables contained. If one of the Python files for + example contains a docstring explaining a particular feature related + to template rendering, it might result in an incorrect example. + + To work around this problem you can use the :ttag:`templatetag` + templatetag to "escape" the various parts of the template syntax. + +.. _source: https://code.djangoproject.com/browser/django/trunk/django/conf/app_template/ + +startproject [destination] +---------------------------------------- .. django-admin:: startproject -Creates a Django project directory structure for the given project name in the -current directory. +Creates a Django project directory structure for the given project name in +the current directory or the given destination. + +.. versionchanged:: 1.4 + +By default the directory created contains a ``manage.py`` file and a project +package (containing ``settings.py`` file and other project template files), +see the `template source`_ for more details. + +If only the project name is given, both the project directory and project +package will be named ```` and the project directory +will be created in the current working directory. + +If the optional destination is provided, it will be used to create the +the new project directory in. The use of '.' to denote the current working +directory is valid for the destination. For example:: + + django-admin.py startproject myproject /Users/jezdez/Code + +.. versionadded:: 1.4 + +Similar to the :djadmin:`startapp` command the ``--template`` option is also +available for specifying a directory or file path of a custom project +template. See the :djadmin:`startapp` documentation for details of supported +project template formats. + +For example, this would look for a project template in the given directory +when creating the ``myproject`` project:: + + django-admin.py startproject --template=/Users/jezdez/Code/my_project_template myproject + +When Django copies the project template files it will also render the files +whose extension matches those passed with the ``--extension`` option (``py`` +by default) using the template engine. The :class:`template context +` used is: + +- Any option passed to the startproject command +- ``project_name`` -- the project name as passed to the command +- ``project_directory`` -- the full path of the newly created project +- ``secret_key`` -- a random key for the :setting:`SECRET_KEY` setting + +Please also see the :ref:`rendering warning ` as mentioned +for :djadmin:`startapp`. + +.. _`template source`: https://code.djangoproject.com/browser/django/trunk/django/conf/project_template/ + syncdb ------ diff --git a/docs/releases/1.4-alpha-1.txt b/docs/releases/1.4-alpha-1.txt index 8050ca91f6b..d1b5f5eccab 100644 --- a/docs/releases/1.4-alpha-1.txt +++ b/docs/releases/1.4-alpha-1.txt @@ -429,6 +429,27 @@ callable :djadmin:`runserver` uses. (The :djadmin:`runfcgi` management command also internally wraps the WSGI callable configured via :setting:`WSGI_APPLICATION`.) +Custom project and app templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :djadmin:`startapp` and :djadmin:`startproject` management commands +got a ``--template`` option for specifying paths or URL to custom app or +project templates. + +For example, Django will use the ``/path/to/my_project_template`` +directorywhen running the following command:: + + django-admin.py startproject --template=/path/to/my_project_template myproject + +Additionally you can now provide a destination directory as the second +argument to both :djadmin:`startapp` and :djadmin:`startproject`:: + + django-admin.py startapp myapp /path/to/new/app + django-admin.py startproject myproject /path/to/new/project + +For more information see the :djadmin:`startapp` and :djadmin:`startproject` +documentation. + Support for time zones ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index 4c1caee309f..93ece428bd9 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -420,6 +420,27 @@ callable :djadmin:`runserver` uses. (The :djadmin:`runfcgi` management command also internally wraps the WSGI callable configured via :setting:`WSGI_APPLICATION`.) +Custom project and app templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :djadmin:`startapp` and :djadmin:`startproject` management commands +got a ``--template`` option for specifying paths or URL to custom app or +project templates. + +For example, Django will use the ``/path/to/my_project_template`` +directorywhen running the following command:: + + django-admin.py startproject --template=/path/to/my_project_template myproject + +Additionally you can now provide a destination directory as the second +argument to both :djadmin:`startapp` and :djadmin:`startproject`:: + + django-admin.py startapp myapp /path/to/new/app + django-admin.py startproject myproject /path/to/new/project + +For more information see the :djadmin:`startapp` and :djadmin:`startproject` +documentation. + Support for time zones ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/regressiontests/admin_scripts/custom_templates/app_template/__init__.py b/tests/regressiontests/admin_scripts/custom_templates/app_template/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regressiontests/admin_scripts/custom_templates/app_template/api.py b/tests/regressiontests/admin_scripts/custom_templates/app_template/api.py new file mode 100644 index 00000000000..439d935f73f --- /dev/null +++ b/tests/regressiontests/admin_scripts/custom_templates/app_template/api.py @@ -0,0 +1 @@ +# your API code \ No newline at end of file diff --git a/tests/regressiontests/admin_scripts/custom_templates/app_template/models.py b/tests/regressiontests/admin_scripts/custom_templates/app_template/models.py new file mode 100644 index 00000000000..92be216cfc9 --- /dev/null +++ b/tests/regressiontests/admin_scripts/custom_templates/app_template/models.py @@ -0,0 +1 @@ +# whatever \ No newline at end of file diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template.tgz b/tests/regressiontests/admin_scripts/custom_templates/project_template.tgz new file mode 100644 index 00000000000..2a181c9faad Binary files /dev/null and b/tests/regressiontests/admin_scripts/custom_templates/project_template.tgz differ diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/additional_file.py b/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/additional_file.py new file mode 100644 index 00000000000..032f0bcd72d --- /dev/null +++ b/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/additional_file.py @@ -0,0 +1 @@ +# some file for {{ project_name }} test project \ No newline at end of file diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/manage.py b/tests/regressiontests/admin_scripts/custom_templates/project_template/manage.py new file mode 100755 index 00000000000..ad8aafe233d --- /dev/null +++ b/tests/regressiontests/admin_scripts/custom_templates/project_template/manage.py @@ -0,0 +1 @@ +# The manage.py of the {{ project_name }} test project \ No newline at end of file diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/project_name/__init__.py b/tests/regressiontests/admin_scripts/custom_templates/project_template/project_name/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/project_name/settings.py b/tests/regressiontests/admin_scripts/custom_templates/project_template/project_name/settings.py new file mode 100644 index 00000000000..ddc216c829f --- /dev/null +++ b/tests/regressiontests/admin_scripts/custom_templates/project_template/project_name/settings.py @@ -0,0 +1 @@ +# Django settings for {{ project_name }} test project. diff --git a/tests/regressiontests/admin_scripts/tests.py b/tests/regressiontests/admin_scripts/tests.py index 72d47adce17..8fd74db556e 100644 --- a/tests/regressiontests/admin_scripts/tests.py +++ b/tests/regressiontests/admin_scripts/tests.py @@ -6,24 +6,29 @@ and default settings.py files. from __future__ import with_statement import os +import re import shutil import socket import sys -import re +import urllib from django import conf, bin, get_version from django.conf import settings from django.test.simple import DjangoTestSuiteRunner from django.utils import unittest +from django.test import LiveServerTestCase + +test_dir = os.path.dirname(os.path.dirname(__file__)) +expected_query_re = re.compile(r'CREATE TABLE [`"]admin_scripts_article[`"]', re.IGNORECASE) class AdminScriptTestCase(unittest.TestCase): def write_settings(self, filename, apps=None, is_dir=False, sdict=None): test_dir = os.path.dirname(os.path.dirname(__file__)) if is_dir: - settings_dir = os.path.join(test_dir,filename) + settings_dir = os.path.join(test_dir, filename) os.mkdir(settings_dir) - settings_file = open(os.path.join(settings_dir,'__init__.py'), 'w') + settings_file = open(os.path.join(settings_dir, '__init__.py'), 'w') else: settings_file = open(os.path.join(test_dir, filename), 'w') settings_file.write('# Settings file automatically generated by regressiontests.admin_scripts test case\n') @@ -50,7 +55,6 @@ class AdminScriptTestCase(unittest.TestCase): settings_file.close() def remove_settings(self, filename, is_dir=False): - test_dir = os.path.dirname(os.path.dirname(__file__)) full_name = os.path.join(test_dir, filename) if is_dir: shutil.rmtree(full_name) @@ -84,7 +88,6 @@ class AdminScriptTestCase(unittest.TestCase): return paths def run_test(self, script, args, settings_file=None, apps=None): - test_dir = os.path.dirname(os.path.dirname(__file__)) project_dir = os.path.dirname(test_dir) base_dir = os.path.dirname(project_dir) ext_backend_base_dirs = self._ext_backend_paths() @@ -142,13 +145,12 @@ class AdminScriptTestCase(unittest.TestCase): def run_django_admin(self, args, settings_file=None): bin_dir = os.path.abspath(os.path.dirname(bin.__file__)) - return self.run_test(os.path.join(bin_dir,'django-admin.py'), args, settings_file) + return self.run_test(os.path.join(bin_dir, 'django-admin.py'), args, settings_file) def run_manage(self, args, settings_file=None): conf_dir = os.path.dirname(conf.__file__) template_manage_py = os.path.join(conf_dir, 'project_template', 'manage.py') - test_dir = os.path.dirname(os.path.dirname(__file__)) test_manage_py = os.path.join(test_dir, 'manage.py') shutil.copyfile(template_manage_py, test_manage_py) @@ -196,22 +198,22 @@ class DjangoAdminNoSettings(AdminScriptTestCase): def test_builtin_command(self): "no settings: django-admin builtin commands fail with an import error when no settings provided" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined') def test_builtin_with_bad_settings(self): "no settings: django-admin builtin commands fail if settings file (from argument) doesn't exist" - args = ['sqlall','--settings=bad_settings', 'admin_scripts'] + args = ['sqlall', '--settings=bad_settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") def test_builtin_with_bad_environment(self): "no settings: django-admin builtin commands fail if settings file (from environment) doesn't exist" - args = ['sqlall','admin_scripts'] - out, err = self.run_django_admin(args,'bad_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_django_admin(args, 'bad_settings') self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") @@ -228,36 +230,36 @@ class DjangoAdminDefaultSettings(AdminScriptTestCase): def test_builtin_command(self): "default: django-admin builtin commands fail with an import error when no settings provided" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined') def test_builtin_with_settings(self): "default: django-admin builtin commands succeed if settings are provided as argument" - args = ['sqlall','--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_environment(self): "default: django-admin builtin commands succeed if settings are provided in the environment" - args = ['sqlall','admin_scripts'] - out, err = self.run_django_admin(args,'regressiontests.settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_django_admin(args, 'regressiontests.settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_bad_settings(self): "default: django-admin builtin commands fail if settings file (from argument) doesn't exist" - args = ['sqlall','--settings=bad_settings', 'admin_scripts'] + args = ['sqlall', '--settings=bad_settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") def test_builtin_with_bad_environment(self): "default: django-admin builtin commands fail if settings file (from environment) doesn't exist" - args = ['sqlall','admin_scripts'] - out, err = self.run_django_admin(args,'bad_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_django_admin(args, 'bad_settings') self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") @@ -278,7 +280,7 @@ class DjangoAdminDefaultSettings(AdminScriptTestCase): def test_custom_command_with_environment(self): "default: django-admin can execute user commands if settings are provided in environment" args = ['noargs_command'] - out, err = self.run_django_admin(args,'regressiontests.settings') + out, err = self.run_django_admin(args, 'regressiontests.settings') self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -294,36 +296,36 @@ class DjangoAdminFullPathDefaultSettings(AdminScriptTestCase): def test_builtin_command(self): "fulldefault: django-admin builtin commands fail with an import error when no settings provided" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined') def test_builtin_with_settings(self): "fulldefault: django-admin builtin commands succeed if a settings file is provided" - args = ['sqlall','--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_environment(self): "fulldefault: django-admin builtin commands succeed if the environment contains settings" - args = ['sqlall','admin_scripts'] - out, err = self.run_django_admin(args,'regressiontests.settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_django_admin(args, 'regressiontests.settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_bad_settings(self): "fulldefault: django-admin builtin commands fail if settings file (from argument) doesn't exist" - args = ['sqlall','--settings=bad_settings', 'admin_scripts'] + args = ['sqlall', '--settings=bad_settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") def test_builtin_with_bad_environment(self): "fulldefault: django-admin builtin commands fail if settings file (from environment) doesn't exist" - args = ['sqlall','admin_scripts'] - out, err = self.run_django_admin(args,'bad_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_django_admin(args, 'bad_settings') self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") @@ -344,7 +346,7 @@ class DjangoAdminFullPathDefaultSettings(AdminScriptTestCase): def test_custom_command_with_environment(self): "fulldefault: django-admin can execute user commands if settings are provided in environment" args = ['noargs_command'] - out, err = self.run_django_admin(args,'regressiontests.settings') + out, err = self.run_django_admin(args, 'regressiontests.settings') self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -353,43 +355,43 @@ class DjangoAdminMinimalSettings(AdminScriptTestCase): doesn't contain the test application. """ def setUp(self): - self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes']) + self.write_settings('settings.py', apps=['django.contrib.auth', 'django.contrib.contenttypes']) def tearDown(self): self.remove_settings('settings.py') def test_builtin_command(self): "minimal: django-admin builtin commands fail with an import error when no settings provided" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined') def test_builtin_with_settings(self): "minimal: django-admin builtin commands fail if settings are provided as argument" - args = ['sqlall','--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, 'App with label admin_scripts could not be found') def test_builtin_with_environment(self): "minimal: django-admin builtin commands fail if settings are provided in the environment" - args = ['sqlall','admin_scripts'] - out, err = self.run_django_admin(args,'regressiontests.settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_django_admin(args, 'regressiontests.settings') self.assertNoOutput(out) self.assertOutput(err, 'App with label admin_scripts could not be found') def test_builtin_with_bad_settings(self): "minimal: django-admin builtin commands fail if settings file (from argument) doesn't exist" - args = ['sqlall','--settings=bad_settings', 'admin_scripts'] + args = ['sqlall', '--settings=bad_settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") def test_builtin_with_bad_environment(self): "minimal: django-admin builtin commands fail if settings file (from environment) doesn't exist" - args = ['sqlall','admin_scripts'] - out, err = self.run_django_admin(args,'bad_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_django_admin(args, 'bad_settings') self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") @@ -410,7 +412,7 @@ class DjangoAdminMinimalSettings(AdminScriptTestCase): def test_custom_command_with_environment(self): "minimal: django-admin can't execute user commands, even if settings are provided in environment" args = ['noargs_command'] - out, err = self.run_django_admin(args,'regressiontests.settings') + out, err = self.run_django_admin(args, 'regressiontests.settings') self.assertNoOutput(out) self.assertOutput(err, "Unknown command: 'noargs_command'") @@ -426,36 +428,36 @@ class DjangoAdminAlternateSettings(AdminScriptTestCase): def test_builtin_command(self): "alternate: django-admin builtin commands fail with an import error when no settings provided" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined') def test_builtin_with_settings(self): "alternate: django-admin builtin commands succeed if settings are provided as argument" - args = ['sqlall','--settings=regressiontests.alternate_settings', 'admin_scripts'] + args = ['sqlall', '--settings=regressiontests.alternate_settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_environment(self): "alternate: django-admin builtin commands succeed if settings are provided in the environment" - args = ['sqlall','admin_scripts'] - out, err = self.run_django_admin(args,'regressiontests.alternate_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_django_admin(args, 'regressiontests.alternate_settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_bad_settings(self): "alternate: django-admin builtin commands fail if settings file (from argument) doesn't exist" - args = ['sqlall','--settings=bad_settings', 'admin_scripts'] + args = ['sqlall', '--settings=bad_settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") def test_builtin_with_bad_environment(self): "alternate: django-admin builtin commands fail if settings file (from environment) doesn't exist" - args = ['sqlall','admin_scripts'] - out, err = self.run_django_admin(args,'bad_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_django_admin(args, 'bad_settings') self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") @@ -476,7 +478,7 @@ class DjangoAdminAlternateSettings(AdminScriptTestCase): def test_custom_command_with_environment(self): "alternate: django-admin can execute user commands if settings are provided in environment" args = ['noargs_command'] - out, err = self.run_django_admin(args,'regressiontests.alternate_settings') + out, err = self.run_django_admin(args, 'regressiontests.alternate_settings') self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -488,7 +490,7 @@ class DjangoAdminMultipleSettings(AdminScriptTestCase): alternate settings must be used by the running script. """ def setUp(self): - self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes']) + self.write_settings('settings.py', apps=['django.contrib.auth', 'django.contrib.contenttypes']) self.write_settings('alternate_settings.py') def tearDown(self): @@ -497,35 +499,35 @@ class DjangoAdminMultipleSettings(AdminScriptTestCase): def test_builtin_command(self): "alternate: django-admin builtin commands fail with an import error when no settings provided" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined') def test_builtin_with_settings(self): "alternate: django-admin builtin commands succeed if settings are provided as argument" - args = ['sqlall','--settings=regressiontests.alternate_settings', 'admin_scripts'] + args = ['sqlall', '--settings=regressiontests.alternate_settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_environment(self): "alternate: django-admin builtin commands succeed if settings are provided in the environment" - args = ['sqlall','admin_scripts'] - out, err = self.run_django_admin(args,'regressiontests.alternate_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_django_admin(args, 'regressiontests.alternate_settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_bad_settings(self): "alternate: django-admin builtin commands fail if settings file (from argument) doesn't exist" - args = ['sqlall','--settings=bad_settings', 'admin_scripts'] + args = ['sqlall', '--settings=bad_settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertOutput(err, "Could not import settings 'bad_settings'") def test_builtin_with_bad_environment(self): "alternate: django-admin builtin commands fail if settings file (from environment) doesn't exist" - args = ['sqlall','admin_scripts'] - out, err = self.run_django_admin(args,'bad_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_django_admin(args, 'bad_settings') self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") @@ -546,7 +548,7 @@ class DjangoAdminMultipleSettings(AdminScriptTestCase): def test_custom_command_with_environment(self): "alternate: django-admin can execute user commands if settings are provided in environment" args = ['noargs_command'] - out, err = self.run_django_admin(args,'regressiontests.alternate_settings') + out, err = self.run_django_admin(args, 'regressiontests.alternate_settings') self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -565,31 +567,41 @@ class DjangoAdminSettingsDirectory(AdminScriptTestCase): def test_setup_environ(self): "directory: startapp creates the correct directory" - test_dir = os.path.dirname(os.path.dirname(__file__)) - args = ['startapp','settings_test'] + args = ['startapp', 'settings_test'] app_path = os.path.join(test_dir, 'settings_test') - out, err = self.run_django_admin(args,'regressiontests.settings') + out, err = self.run_django_admin(args, 'regressiontests.settings') self.addCleanup(shutil.rmtree, app_path) self.assertNoOutput(err) self.assertTrue(os.path.exists(app_path)) + def test_setup_environ_custom_template(self): + "directory: startapp creates the correct directory with a custom template" + template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'app_template') + args = ['startapp', '--template', template_path, 'custom_settings_test'] + app_path = os.path.join(test_dir, 'custom_settings_test') + out, err = self.run_django_admin(args, 'regressiontests.settings') + self.addCleanup(shutil.rmtree, app_path) + self.assertNoOutput(err) + self.assertTrue(os.path.exists(app_path)) + self.assertTrue(os.path.exists(os.path.join(app_path, 'api.py'))) + def test_builtin_command(self): "directory: django-admin builtin commands fail with an import error when no settings provided" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined') def test_builtin_with_bad_settings(self): "directory: django-admin builtin commands fail if settings file (from argument) doesn't exist" - args = ['sqlall','--settings=bad_settings', 'admin_scripts'] + args = ['sqlall', '--settings=bad_settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertOutput(err, "Could not import settings 'bad_settings'") def test_builtin_with_bad_environment(self): "directory: django-admin builtin commands fail if settings file (from environment) doesn't exist" - args = ['sqlall','admin_scripts'] - out, err = self.run_django_admin(args,'bad_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_django_admin(args, 'bad_settings') self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") @@ -602,15 +614,15 @@ class DjangoAdminSettingsDirectory(AdminScriptTestCase): def test_builtin_with_settings(self): "directory: django-admin builtin commands succeed if settings are provided as argument" - args = ['sqlall','--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_environment(self): "directory: django-admin builtin commands succeed if settings are provided in the environment" - args = ['sqlall','admin_scripts'] - out, err = self.run_django_admin(args,'regressiontests.settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_django_admin(args, 'regressiontests.settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -626,22 +638,22 @@ class ManageNoSettings(AdminScriptTestCase): def test_builtin_command(self): "no settings: manage.py builtin commands fail with an import error when no settings provided" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'regressiontests.settings'") def test_builtin_with_bad_settings(self): "no settings: manage.py builtin commands fail if settings file (from argument) doesn't exist" - args = ['sqlall','--settings=bad_settings', 'admin_scripts'] + args = ['sqlall', '--settings=bad_settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") def test_builtin_with_bad_environment(self): "no settings: manage.py builtin commands fail if settings file (from environment) doesn't exist" - args = ['sqlall','admin_scripts'] - out, err = self.run_manage(args,'bad_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_manage(args, 'bad_settings') self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") @@ -658,36 +670,36 @@ class ManageDefaultSettings(AdminScriptTestCase): def test_builtin_command(self): "default: manage.py builtin commands succeed when default settings are appropriate" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_settings(self): "default: manage.py builtin commands succeed if settings are provided as argument" - args = ['sqlall','--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_environment(self): "default: manage.py builtin commands succeed if settings are provided in the environment" - args = ['sqlall','admin_scripts'] - out, err = self.run_manage(args,'regressiontests.settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_manage(args, 'regressiontests.settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_bad_settings(self): "default: manage.py builtin commands succeed if settings file (from argument) doesn't exist" - args = ['sqlall','--settings=bad_settings', 'admin_scripts'] + args = ['sqlall', '--settings=bad_settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") def test_builtin_with_bad_environment(self): "default: manage.py builtin commands fail if settings file (from environment) doesn't exist" - args = ['sqlall','admin_scripts'] - out, err = self.run_manage(args,'bad_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_manage(args, 'bad_settings') self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") @@ -708,7 +720,7 @@ class ManageDefaultSettings(AdminScriptTestCase): def test_custom_command_with_environment(self): "default: manage.py can execute user commands when settings are provided in environment" args = ['noargs_command'] - out, err = self.run_manage(args,'regressiontests.settings') + out, err = self.run_manage(args, 'regressiontests.settings') self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -725,36 +737,36 @@ class ManageFullPathDefaultSettings(AdminScriptTestCase): def test_builtin_command(self): "fulldefault: manage.py builtin commands succeed when default settings are appropriate" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_settings(self): "fulldefault: manage.py builtin commands succeed if settings are provided as argument" - args = ['sqlall','--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_environment(self): "fulldefault: manage.py builtin commands succeed if settings are provided in the environment" - args = ['sqlall','admin_scripts'] - out, err = self.run_manage(args,'regressiontests.settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_manage(args, 'regressiontests.settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_bad_settings(self): "fulldefault: manage.py builtin commands succeed if settings file (from argument) doesn't exist" - args = ['sqlall','--settings=bad_settings', 'admin_scripts'] + args = ['sqlall', '--settings=bad_settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") def test_builtin_with_bad_environment(self): "fulldefault: manage.py builtin commands fail if settings file (from environment) doesn't exist" - args = ['sqlall','admin_scripts'] - out, err = self.run_manage(args,'bad_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_manage(args, 'bad_settings') self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") @@ -775,7 +787,7 @@ class ManageFullPathDefaultSettings(AdminScriptTestCase): def test_custom_command_with_environment(self): "fulldefault: manage.py can execute user commands when settings are provided in environment" args = ['noargs_command'] - out, err = self.run_manage(args,'regressiontests.settings') + out, err = self.run_manage(args, 'regressiontests.settings') self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -784,43 +796,43 @@ class ManageMinimalSettings(AdminScriptTestCase): doesn't contain the test application. """ def setUp(self): - self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes']) + self.write_settings('settings.py', apps=['django.contrib.auth', 'django.contrib.contenttypes']) def tearDown(self): self.remove_settings('settings.py') def test_builtin_command(self): "minimal: manage.py builtin commands fail with an import error when no settings provided" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, 'App with label admin_scripts could not be found') def test_builtin_with_settings(self): "minimal: manage.py builtin commands fail if settings are provided as argument" - args = ['sqlall','--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, 'App with label admin_scripts could not be found') def test_builtin_with_environment(self): "minimal: manage.py builtin commands fail if settings are provided in the environment" - args = ['sqlall','admin_scripts'] - out, err = self.run_manage(args,'regressiontests.settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_manage(args, 'regressiontests.settings') self.assertNoOutput(out) self.assertOutput(err, 'App with label admin_scripts could not be found') def test_builtin_with_bad_settings(self): "minimal: manage.py builtin commands fail if settings file (from argument) doesn't exist" - args = ['sqlall','--settings=bad_settings', 'admin_scripts'] + args = ['sqlall', '--settings=bad_settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") def test_builtin_with_bad_environment(self): "minimal: manage.py builtin commands fail if settings file (from environment) doesn't exist" - args = ['sqlall','admin_scripts'] - out, err = self.run_manage(args,'bad_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_manage(args, 'bad_settings') self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") @@ -841,7 +853,7 @@ class ManageMinimalSettings(AdminScriptTestCase): def test_custom_command_with_environment(self): "minimal: manage.py can't execute user commands, even if settings are provided in environment" args = ['noargs_command'] - out, err = self.run_manage(args,'regressiontests.settings') + out, err = self.run_manage(args, 'regressiontests.settings') self.assertNoOutput(out) self.assertOutput(err, "Unknown command: 'noargs_command'") @@ -857,38 +869,36 @@ class ManageAlternateSettings(AdminScriptTestCase): def test_builtin_command(self): "alternate: manage.py builtin commands fail with an import error when no default settings provided" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'regressiontests.settings'") def test_builtin_with_settings(self): "alternate: manage.py builtin commands work with settings provided as argument" - args = ['sqlall','--settings=alternate_settings', 'admin_scripts'] + args = ['sqlall', '--settings=alternate_settings', 'admin_scripts'] out, err = self.run_manage(args) - expected_query_re = re.compile('CREATE TABLE [`"]admin_scripts_article[`"]', re.IGNORECASE) self.assertRegexpMatches(out, expected_query_re) self.assertNoOutput(err) def test_builtin_with_environment(self): "alternate: manage.py builtin commands work if settings are provided in the environment" - args = ['sqlall','admin_scripts'] - out, err = self.run_manage(args,'alternate_settings') - expected_query_re = re.compile('CREATE TABLE [`"]admin_scripts_article[`"]', re.IGNORECASE) + args = ['sqlall', 'admin_scripts'] + out, err = self.run_manage(args, 'alternate_settings') self.assertRegexpMatches(out, expected_query_re) self.assertNoOutput(err) def test_builtin_with_bad_settings(self): "alternate: manage.py builtin commands fail if settings file (from argument) doesn't exist" - args = ['sqlall','--settings=bad_settings', 'admin_scripts'] + args = ['sqlall', '--settings=bad_settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") def test_builtin_with_bad_environment(self): "alternate: manage.py builtin commands fail if settings file (from environment) doesn't exist" - args = ['sqlall','admin_scripts'] - out, err = self.run_manage(args,'bad_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_manage(args, 'bad_settings') self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") @@ -909,7 +919,7 @@ class ManageAlternateSettings(AdminScriptTestCase): def test_custom_command_with_environment(self): "alternate: manage.py can execute user commands if settings are provided in environment" args = ['noargs_command'] - out, err = self.run_manage(args,'alternate_settings') + out, err = self.run_manage(args, 'alternate_settings') self.assertOutput(out, "EXECUTE:NoArgsCommand options=[('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]") self.assertNoOutput(err) @@ -921,7 +931,7 @@ class ManageMultipleSettings(AdminScriptTestCase): alternate settings must be used by the running script. """ def setUp(self): - self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes']) + self.write_settings('settings.py', apps=['django.contrib.auth', 'django.contrib.contenttypes']) self.write_settings('alternate_settings.py') def tearDown(self): @@ -930,36 +940,36 @@ class ManageMultipleSettings(AdminScriptTestCase): def test_builtin_command(self): "multiple: manage.py builtin commands fail with an import error when no settings provided" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, 'App with label admin_scripts could not be found.') def test_builtin_with_settings(self): "multiple: manage.py builtin commands succeed if settings are provided as argument" - args = ['sqlall','--settings=alternate_settings', 'admin_scripts'] + args = ['sqlall', '--settings=alternate_settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_environment(self): "multiple: manage.py can execute builtin commands if settings are provided in the environment" - args = ['sqlall','admin_scripts'] - out, err = self.run_manage(args,'alternate_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_manage(args, 'alternate_settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') def test_builtin_with_bad_settings(self): "multiple: manage.py builtin commands fail if settings file (from argument) doesn't exist" - args = ['sqlall','--settings=bad_settings', 'admin_scripts'] + args = ['sqlall', '--settings=bad_settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") def test_builtin_with_bad_environment(self): "multiple: manage.py builtin commands fail if settings file (from environment) doesn't exist" - args = ['sqlall','admin_scripts'] - out, err = self.run_manage(args,'bad_settings') + args = ['sqlall', 'admin_scripts'] + out, err = self.run_manage(args, 'bad_settings') self.assertNoOutput(out) self.assertOutput(err, "Could not import settings 'bad_settings'") @@ -980,7 +990,7 @@ class ManageMultipleSettings(AdminScriptTestCase): def test_custom_command_with_environment(self): "multiple: manage.py can execute user commands if settings are provided in environment" args = ['noargs_command'] - out, err = self.run_manage(args,'alternate_settings') + out, err = self.run_manage(args, 'alternate_settings') self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -995,11 +1005,10 @@ class ManageSettingsWithImportError(AdminScriptTestCase): self.remove_settings('settings.py') def write_settings_with_import_error(self, filename, apps=None, is_dir=False, sdict=None): - test_dir = os.path.dirname(os.path.dirname(__file__)) if is_dir: - settings_dir = os.path.join(test_dir,filename) + settings_dir = os.path.join(test_dir, filename) os.mkdir(settings_dir) - settings_file = open(os.path.join(settings_dir,'__init__.py'), 'w') + settings_file = open(os.path.join(settings_dir, '__init__.py'), 'w') else: settings_file = open(os.path.join(test_dir, filename), 'w') settings_file.write('# Settings file automatically generated by regressiontests.admin_scripts test case\n') @@ -1009,7 +1018,7 @@ class ManageSettingsWithImportError(AdminScriptTestCase): def test_builtin_command(self): "import error: manage.py builtin commands shows useful diagnostic info when settings with import errors is provided" - args = ['sqlall','admin_scripts'] + args = ['sqlall', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, "No module named foo42bar") @@ -1200,14 +1209,14 @@ class CommandTypes(AdminScriptTestCase): def test_specific_help(self): "--help can be used on a specific command" - args = ['sqlall','--help'] + args = ['sqlall', '--help'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "Prints the CREATE TABLE, custom SQL and CREATE INDEX SQL statements for the given model module name(s).") def test_base_command(self): "User BaseCommands can execute when a label is provided" - args = ['base_command','testlabel'] + args = ['base_command', 'testlabel'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', '1'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]") @@ -1221,21 +1230,21 @@ class CommandTypes(AdminScriptTestCase): def test_base_command_multiple_label(self): "User BaseCommands can execute when no labels are provided" - args = ['base_command','testlabel','anotherlabel'] + args = ['base_command', 'testlabel', 'anotherlabel'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel', 'anotherlabel'), options=[('option_a', '1'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]") def test_base_command_with_option(self): "User BaseCommands can execute with options when a label is provided" - args = ['base_command','testlabel','--option_a=x'] + args = ['base_command', 'testlabel', '--option_a=x'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]") def test_base_command_with_options(self): "User BaseCommands can execute with multiple options when a label is provided" - args = ['base_command','testlabel','-a','x','--option_b=y'] + args = ['base_command', 'testlabel', '-a', 'x', '--option_b=y'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', 'y'), ('option_c', '3'), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]") @@ -1249,7 +1258,7 @@ class CommandTypes(AdminScriptTestCase): def test_noargs_with_args(self): "NoArg Commands raise an error if an argument is provided" - args = ['noargs_command','argument'] + args = ['noargs_command', 'argument'] out, err = self.run_manage(args) self.assertOutput(err, "Error: Command doesn't accept any arguments") @@ -1259,7 +1268,7 @@ class CommandTypes(AdminScriptTestCase): out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:AppCommand app=, options=[('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]") def test_app_command_no_apps(self): @@ -1270,14 +1279,14 @@ class CommandTypes(AdminScriptTestCase): def test_app_command_multiple_apps(self): "User AppCommands raise an error when multiple app names are provided" - args = ['app_command','auth','contenttypes'] + args = ['app_command', 'auth', 'contenttypes'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:AppCommand app=, options=[('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]") self.assertOutput(out, "EXECUTE:AppCommand app=, options=[('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]") def test_app_command_invalid_appname(self): @@ -1294,7 +1303,7 @@ class CommandTypes(AdminScriptTestCase): def test_label_command(self): "User LabelCommands can execute when a label is provided" - args = ['label_command','testlabel'] + args = ['label_command', 'testlabel'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:LabelCommand label=testlabel, options=[('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]") @@ -1307,7 +1316,7 @@ class CommandTypes(AdminScriptTestCase): def test_label_command_multiple_label(self): "User LabelCommands are executed multiple times if multiple labels are provided" - args = ['label_command','testlabel','anotherlabel'] + args = ['label_command', 'testlabel', 'anotherlabel'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:LabelCommand label=testlabel, options=[('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]") @@ -1323,7 +1332,7 @@ class ArgumentOrder(AdminScriptTestCase): individual command. """ def setUp(self): - self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes']) + self.write_settings('settings.py', apps=['django.contrib.auth', 'django.contrib.contenttypes']) self.write_settings('alternate_settings.py') def tearDown(self): @@ -1332,35 +1341,113 @@ class ArgumentOrder(AdminScriptTestCase): def test_setting_then_option(self): "Options passed after settings are correctly handled" - args = ['base_command','testlabel','--settings=alternate_settings','--option_a=x'] + args = ['base_command', 'testlabel', '--settings=alternate_settings', '--option_a=x'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', None), ('verbosity', '1')]") def test_setting_then_short_option(self): "Short options passed after settings are correctly handled" - args = ['base_command','testlabel','--settings=alternate_settings','--option_a=x'] + args = ['base_command', 'testlabel', '--settings=alternate_settings', '--option_a=x'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', None), ('verbosity', '1')]") def test_option_then_setting(self): "Options passed before settings are correctly handled" - args = ['base_command','testlabel','--option_a=x','--settings=alternate_settings'] + args = ['base_command', 'testlabel', '--option_a=x', '--settings=alternate_settings'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', None), ('verbosity', '1')]") def test_short_option_then_setting(self): "Short options passed before settings are correctly handled" - args = ['base_command','testlabel','-a','x','--settings=alternate_settings'] + args = ['base_command', 'testlabel', '-a', 'x', '--settings=alternate_settings'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', None), ('verbosity', '1')]") def test_option_then_setting_then_option(self): "Options are correctly handled when they are passed before and after a setting" - args = ['base_command','testlabel','--option_a=x','--settings=alternate_settings','--option_b=y'] + args = ['base_command', 'testlabel', '--option_a=x', '--settings=alternate_settings', '--option_b=y'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', 'y'), ('option_c', '3'), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', None), ('verbosity', '1')]") + + +class StartProject(LiveServerTestCase, AdminScriptTestCase): + + def test_wrong_args(self): + "Make sure passing the wrong kinds of arguments raises a CommandError" + out, err = self.run_django_admin(['startproject']) + self.assertNoOutput(out) + self.assertOutput(err, "you must provide a project name") + + def test_simple_project(self): + "Make sure the startproject management command creates a project" + args = ['startproject', 'testproject'] + testproject_dir = os.path.join(test_dir, 'testproject') + + out, err = self.run_django_admin(args) + self.assertNoOutput(err) + self.assertTrue(os.path.isdir(testproject_dir)) + self.addCleanup(shutil.rmtree, testproject_dir) + + # running again.. + out, err = self.run_django_admin(args) + self.assertNoOutput(out) + self.assertOutput(err, "File exists") + + def test_simple_project_different_directory(self): + "Make sure the startproject management command creates a project in a specific directory" + args = ['startproject', 'testproject', 'othertestproject'] + testproject_dir = os.path.join(test_dir, 'othertestproject') + + out, err = self.run_django_admin(args) + self.assertNoOutput(err) + self.assertTrue(os.path.isdir(os.path.join(testproject_dir, 'testproject'))) + self.assertTrue(os.path.exists(os.path.join(testproject_dir, 'testproject', 'manage.py'))) + self.addCleanup(shutil.rmtree, testproject_dir) + + # running again.. + out, err = self.run_django_admin(args) + self.assertNoOutput(out) + self.assertOutput(err, "File exists") + + def test_custom_project_template(self): + "Make sure the startproject management command is able to use a different project template" + template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template') + args = ['startproject', '--template', template_path, 'customtestproject'] + testproject_dir = os.path.join(test_dir, 'customtestproject') + + out, err = self.run_django_admin(args) + self.assertNoOutput(err) + self.assertTrue(os.path.isdir(testproject_dir)) + self.addCleanup(shutil.rmtree, testproject_dir) + self.assertTrue(os.path.exists(os.path.join(testproject_dir, 'additional_dir'))) + + def test_custom_project_template_from_tarball_by_path(self): + "Make sure the startproject management command is able to use a different project template from a tarball" + template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template.tgz') + args = ['startproject', '--template', template_path, 'tarballtestproject'] + testproject_dir = os.path.join(test_dir, 'tarballtestproject') + + out, err = self.run_django_admin(args) + self.assertNoOutput(err) + self.assertTrue(os.path.isdir(testproject_dir)) + self.addCleanup(shutil.rmtree, testproject_dir) + self.assertTrue(os.path.exists(os.path.join(testproject_dir, 'run.py'))) + + def test_custom_project_template_from_tarball_by_url(self): + "Make sure the startproject management command is able to use a different project template from a tarball via a url" + template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template.tgz') + template_url = '%s/admin_scripts/custom_templates/project_template.tgz' % self.live_server_url + + args = ['startproject', '--template', template_url, 'urltestproject'] + testproject_dir = os.path.join(test_dir, 'urltestproject') + + out, err = self.run_django_admin(args) + self.assertNoOutput(err) + self.assertTrue(os.path.isdir(testproject_dir)) + self.addCleanup(shutil.rmtree, testproject_dir) + self.assertTrue(os.path.exists(os.path.join(testproject_dir, 'run.py'))) diff --git a/tests/regressiontests/admin_scripts/urls.py b/tests/regressiontests/admin_scripts/urls.py new file mode 100644 index 00000000000..692638cecaf --- /dev/null +++ b/tests/regressiontests/admin_scripts/urls.py @@ -0,0 +1,10 @@ +import os +from django.conf.urls import patterns + +here = os.path.dirname(__file__) + +urlpatterns = patterns('', + (r'^custom_templates/(?P.*)$', 'django.views.static.serve', { + 'document_root': os.path.join(here, 'custom_templates'), + }), +) diff --git a/tests/regressiontests/utils/archive.py b/tests/regressiontests/utils/archive.py new file mode 100644 index 00000000000..0927c624f3e --- /dev/null +++ b/tests/regressiontests/utils/archive.py @@ -0,0 +1,69 @@ +import os +import shutil +import tempfile +from django.utils import unittest + +from django.utils.archive import Archive, extract + + +TEST_DIR = os.path.join(os.path.dirname(__file__), 'archives') + + +class ArchiveTester(object): + archive = None + + def setUp(self): + """ + Create temporary directory for testing extraction. + """ + self.old_cwd = os.getcwd() + self.tmpdir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.tmpdir) + self.archive_path = os.path.join(TEST_DIR, self.archive) + # Always start off in TEST_DIR. + os.chdir(TEST_DIR) + + def tearDown(self): + os.chdir(self.old_cwd) + + def test_extract_method(self): + Archive(self.archive).extract(self.tmpdir) + self.check_files(self.tmpdir) + + def test_extract_method_no_to_path(self): + os.chdir(self.tmpdir) + Archive(self.archive_path).extract() + self.check_files(self.tmpdir) + + def test_extract_function(self): + extract(self.archive_path, self.tmpdir) + self.check_files(self.tmpdir) + + def test_extract_function_no_to_path(self): + os.chdir(self.tmpdir) + extract(self.archive_path) + self.check_files(self.tmpdir) + + def check_files(self, tmpdir): + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, '1'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, '2'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', '1'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', '2'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', 'bar', '1'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', 'bar', '2'))) + + +class TestZip(ArchiveTester, unittest.TestCase): + archive = 'foobar.zip' + + +class TestTar(ArchiveTester, unittest.TestCase): + archive = 'foobar.tar' + + +class TestGzipTar(ArchiveTester, unittest.TestCase): + archive = 'foobar.tar.gz' + + +class TestBzip2Tar(ArchiveTester, unittest.TestCase): + archive = 'foobar.tar.bz2' diff --git a/tests/regressiontests/utils/archives/foobar.tar b/tests/regressiontests/utils/archives/foobar.tar new file mode 100644 index 00000000000..5418405070f Binary files /dev/null and b/tests/regressiontests/utils/archives/foobar.tar differ diff --git a/tests/regressiontests/utils/archives/foobar.tar.bz2 b/tests/regressiontests/utils/archives/foobar.tar.bz2 new file mode 100644 index 00000000000..693529dcefa Binary files /dev/null and b/tests/regressiontests/utils/archives/foobar.tar.bz2 differ diff --git a/tests/regressiontests/utils/archives/foobar.tar.gz b/tests/regressiontests/utils/archives/foobar.tar.gz new file mode 100644 index 00000000000..5b596bd58ef Binary files /dev/null and b/tests/regressiontests/utils/archives/foobar.tar.gz differ diff --git a/tests/regressiontests/utils/archives/foobar.zip b/tests/regressiontests/utils/archives/foobar.zip new file mode 100644 index 00000000000..dc3e2fc0675 Binary files /dev/null and b/tests/regressiontests/utils/archives/foobar.zip differ diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index b72a88b33d5..b80b1105850 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -24,3 +24,4 @@ from .baseconv import TestBaseConv from .jslex import JsTokensTest, JsToCForGettextTest from .ipv6 import TestUtilsIPv6 from .timezone import TimezoneTests +from .archive import TestZip, TestTar, TestGzipTar, TestBzip2Tar diff --git a/tests/urls.py b/tests/urls.py index e7c23e5144d..4cd603e29f9 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -27,4 +27,7 @@ urlpatterns = patterns('', # admin custom URL tests (r'^custom_urls/', include('regressiontests.admin_custom_urls.urls')), + # admin custom URL tests + (r'^admin_scripts/', include('regressiontests.admin_scripts.urls')), + )