mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2024-11-22 09:36:52 +03:00
add new "utils" app to make "startapp2" management command available
This commit is contained in:
parent
e4d94b52d5
commit
b3271b9404
|
@ -53,6 +53,7 @@ THIRD_PARTY_APPS = [
|
||||||
|
|
||||||
# Apps specific for this project go here.
|
# Apps specific for this project go here.
|
||||||
LOCAL_APPS = [
|
LOCAL_APPS = [
|
||||||
|
'{{ cookiecutter.project_slug }}.utils.apps.UtilsConfig',
|
||||||
# custom users app
|
# custom users app
|
||||||
'{{ cookiecutter.project_slug }}.users.apps.UsersConfig',
|
'{{ cookiecutter.project_slug }}.users.apps.UsersConfig',
|
||||||
# Your stuff: custom apps go here
|
# Your stuff: custom apps go here
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class UtilsConfig(AppConfig):
|
||||||
|
name = '{{cookiecutter.project_slug}}.utils'
|
||||||
|
verbose_name = "Utils"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
"""Override this to put in:
|
||||||
|
Utils system checks
|
||||||
|
Utils signal registration
|
||||||
|
"""
|
||||||
|
pass
|
|
@ -0,0 +1,170 @@
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from importlib import import_module
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
import django
|
||||||
|
from django.core.management.base import CommandError
|
||||||
|
from django.core.management.templates import TemplateCommand
|
||||||
|
from django.core.management.utils import handle_extensions
|
||||||
|
from django.template import Context, Engine
|
||||||
|
from django.utils.version import get_docs_version
|
||||||
|
|
||||||
|
# Setup a stub settings environment for template rendering
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
if not settings.configured:
|
||||||
|
settings.configure()
|
||||||
|
|
||||||
|
COOKIECUTTER_DJANGO_APPS_PATH = str(settings.APPS_DIR)
|
||||||
|
COOKIECUTTER_DJANGO_APPS_DIR_NAME = os.path.basename(COOKIECUTTER_DJANGO_APPS_PATH)
|
||||||
|
COOKIECUTTER_DJANGO_BASE_SETTINGS_FILE = str(settings.ROOT_DIR.path('config', 'settings', 'base.py'))
|
||||||
|
|
||||||
|
|
||||||
|
class Command(TemplateCommand):
|
||||||
|
help = (
|
||||||
|
"Creates a Django app directory structure for the given app name in "
|
||||||
|
"the {dir}/ directory.".format(dir=COOKIECUTTER_DJANGO_APPS_PATH)
|
||||||
|
)
|
||||||
|
missing_args_message = "You must provide an application name."
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('name', help='Name of the application or project.')
|
||||||
|
# parser.add_argument('directory', nargs='?', help='Optional destination directory')
|
||||||
|
parser.add_argument('--template',
|
||||||
|
help='The path or URL to load the template from.')
|
||||||
|
parser.add_argument('--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.')
|
||||||
|
parser.add_argument('--name', '-n', dest='files',
|
||||||
|
action='append', default=[],
|
||||||
|
help='The file name(s) to render. '
|
||||||
|
'Separate multiple extensions with commas, or use '
|
||||||
|
'-n multiple times.')
|
||||||
|
|
||||||
|
def handle(self, **options):
|
||||||
|
name = options.pop('name')
|
||||||
|
self.app_or_project = 'app'
|
||||||
|
self.paths_to_remove = []
|
||||||
|
self.verbosity = options['verbosity']
|
||||||
|
|
||||||
|
self.validate_name(name, self.app_or_project)
|
||||||
|
dot_path_base_name = '{}.{}'.format(COOKIECUTTER_DJANGO_APPS_DIR_NAME, name)
|
||||||
|
# Check that the app_name cannot be imported.
|
||||||
|
try:
|
||||||
|
import_module(dot_path_base_name)
|
||||||
|
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." % name
|
||||||
|
)
|
||||||
|
|
||||||
|
top_dir = path.join(COOKIECUTTER_DJANGO_APPS_PATH, name)
|
||||||
|
try:
|
||||||
|
os.makedirs(top_dir)
|
||||||
|
except FileExistsError:
|
||||||
|
raise CommandError("'%s' already exists" % top_dir)
|
||||||
|
except OSError as e:
|
||||||
|
raise CommandError(e)
|
||||||
|
|
||||||
|
extensions = tuple(handle_extensions(options['extensions']))
|
||||||
|
extra_files = []
|
||||||
|
for file in options['files']:
|
||||||
|
extra_files.extend(map(lambda x: x.strip(), file.split(',')))
|
||||||
|
if self.verbosity >= 2:
|
||||||
|
self.stdout.write("Rendering %s template files with "
|
||||||
|
"extensions: %s\n" %
|
||||||
|
(self.app_or_project, ', '.join(extensions)))
|
||||||
|
self.stdout.write("Rendering %s template files with "
|
||||||
|
"filenames: %s\n" %
|
||||||
|
(self.app_or_project, ', '.join(extra_files)))
|
||||||
|
|
||||||
|
base_name = '%s_name' % self.app_or_project
|
||||||
|
base_subdir = '%s_template' % self.app_or_project
|
||||||
|
base_directory = '%s_directory' % self.app_or_project
|
||||||
|
camel_case_name = 'camel_case_%s_name' % self.app_or_project
|
||||||
|
camel_case_value = ''.join(x for x in name.title() if x != '_')
|
||||||
|
|
||||||
|
context = Context(dict(options, **{
|
||||||
|
base_name: dot_path_base_name,
|
||||||
|
base_directory: top_dir,
|
||||||
|
camel_case_name: camel_case_value,
|
||||||
|
'docs_version': get_docs_version(),
|
||||||
|
'django_version': django.__version__,
|
||||||
|
}), autoescape=False)
|
||||||
|
|
||||||
|
template_dir = self.handle_template(options['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('.') or dirname == '__pycache__':
|
||||||
|
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))
|
||||||
|
for old_suffix, new_suffix in self.rewrite_template_suffixes:
|
||||||
|
if new_path.endswith(old_suffix):
|
||||||
|
new_path = new_path[:-len(old_suffix)] + new_suffix
|
||||||
|
break # Only rewrite once
|
||||||
|
|
||||||
|
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
|
||||||
|
if new_path.endswith(extensions) or filename in extra_files:
|
||||||
|
with open(old_path, 'r', encoding='utf-8') as template_file:
|
||||||
|
content = template_file.read()
|
||||||
|
template = Engine().from_string(content)
|
||||||
|
content = template.render(context)
|
||||||
|
with open(new_path, 'w', encoding='utf-8') as new_file:
|
||||||
|
new_file.write(content)
|
||||||
|
else:
|
||||||
|
shutil.copyfile(old_path, new_path)
|
||||||
|
|
||||||
|
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:
|
||||||
|
self.stderr.write(
|
||||||
|
"Notice: Couldn't set permission bits on %s. You're "
|
||||||
|
"probably using an uncommon filesystem setup. No "
|
||||||
|
"problem." % new_path, self.style.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 path.isfile(path_to_remove):
|
||||||
|
os.remove(path_to_remove)
|
||||||
|
else:
|
||||||
|
shutil.rmtree(path_to_remove)
|
||||||
|
|
||||||
|
msg = "'{dot_path_base_name}.apps.{camel_case_value}Config',".format(dot_path_base_name=dot_path_base_name,
|
||||||
|
camel_case_value=camel_case_value)
|
||||||
|
self.stdout.write(
|
||||||
|
'please, add:\n\n{}\n\nto your LOCAL_APPS on {}\n'.format(msg, COOKIECUTTER_DJANGO_BASE_SETTINGS_FILE))
|
Loading…
Reference in New Issue
Block a user