1
0
mirror of https://github.com/django/django.git synced 2025-10-26 15:16:09 +00:00

Fixed #17042 -- Extended startproject and startapp management commands to better handle custom app and project templates. Many thanks to Preston Holmes for his initial patch and Alex Gaynor, Carl Meyer, Donald Stufft, Jacob Kaplan-Moss and Julien Phalip for code reviewing.

* Added ability to pass the project or app directory path as the second argument
* Added ``--template`` option for specifying custom project and app templates
* Cleaned up admin_scripts tests a little while I was there

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17246 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jannis Leidel
2011-12-22 22:38:02 +00:00
parent 98c974c70b
commit a9a0f0b03f
29 changed files with 996 additions and 266 deletions

View File

@@ -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)