Fixed #32309 -- Added --exclude option to startapp/startproject management commands.

This commit is contained in:
sage 2021-07-24 07:09:03 +07:00 committed by Mariusz Felisiak
parent 3686077d46
commit 84c7c4a477
4 changed files with 100 additions and 1 deletions

View File

@ -1,3 +1,4 @@
import argparse
import cgi import cgi
import mimetypes import mimetypes
import os import os
@ -54,6 +55,14 @@ class TemplateCommand(BaseCommand):
help='The file name(s) to render. Separate multiple file names ' help='The file name(s) to render. Separate multiple file names '
'with commas, or use -n multiple times.' 'with commas, or use -n multiple times.'
) )
parser.add_argument(
'--exclude', '-x',
action='append', default=argparse.SUPPRESS, nargs='?', const='',
help=(
'The directory name(s) to exclude, in addition to .git and '
'__pycache__. Can be used multiple times.'
),
)
def handle(self, app_or_project, name, target=None, **options): def handle(self, app_or_project, name, target=None, **options):
self.app_or_project = app_or_project self.app_or_project = app_or_project
@ -82,8 +91,12 @@ class TemplateCommand(BaseCommand):
extensions = tuple(handle_extensions(options['extensions'])) extensions = tuple(handle_extensions(options['extensions']))
extra_files = [] extra_files = []
excluded_directories = ['.git', '__pycache__']
for file in options['files']: for file in options['files']:
extra_files.extend(map(lambda x: x.strip(), file.split(','))) extra_files.extend(map(lambda x: x.strip(), file.split(',')))
if exclude := options.get('exclude'):
for directory in exclude:
excluded_directories.append(directory.strip())
if self.verbosity >= 2: if self.verbosity >= 2:
self.stdout.write( self.stdout.write(
'Rendering %s template files with extensions: %s' 'Rendering %s template files with extensions: %s'
@ -126,7 +139,10 @@ class TemplateCommand(BaseCommand):
os.makedirs(target_dir, exist_ok=True) os.makedirs(target_dir, exist_ok=True)
for dirname in dirs[:]: for dirname in dirs[:]:
if dirname.startswith('.') or dirname == '__pycache__': if 'exclude' not in options:
if dirname.startswith('.') or dirname == '__pycache__':
dirs.remove(dirname)
elif dirname in excluded_directories:
dirs.remove(dirname) dirs.remove(dirname)
for filename in files: for filename in files:

View File

@ -1304,6 +1304,14 @@ Specifies which files in the app template (in addition to those matching
``--extension``) should be rendered with the template engine. Defaults to an ``--extension``) should be rendered with the template engine. Defaults to an
empty list. empty list.
.. django-admin-option:: --exclude DIRECTORIES, -x DIRECTORIES
.. versionadded:: 4.0
Specifies which directories in the app template should be excluded, in addition
to ``.git`` and ``__pycache__``. If this option is not provided, directories
named ``__pycache__`` or starting with ``.`` will be excluded.
The :class:`template context <django.template.Context>` used for all matching The :class:`template context <django.template.Context>` used for all matching
files is: files is:
@ -1373,6 +1381,14 @@ Specifies which files in the project template (in addition to those matching
``--extension``) should be rendered with the template engine. Defaults to an ``--extension``) should be rendered with the template engine. Defaults to an
empty list. empty list.
.. django-admin-option:: --exclude DIRECTORIES, -x DIRECTORIES
.. versionadded:: 4.0
Specifies which directories in the project template should be excluded, in
addition to ``.git`` and ``__pycache__``. If this option is not provided,
directories named ``__pycache__`` or starting with ``.`` will be excluded.
The :class:`template context <django.template.Context>` used is: The :class:`template context <django.template.Context>` used is:
- Any option passed to the ``startproject`` command (among the command's - Any option passed to the ``startproject`` command (among the command's

View File

@ -278,6 +278,9 @@ Management Commands
<django.core.management.BaseCommand.suppressed_base_arguments>` attribute <django.core.management.BaseCommand.suppressed_base_arguments>` attribute
allows suppressing unsupported default command options in the help output. allows suppressing unsupported default command options in the help output.
* The new :option:`startapp --exclude` and :option:`startproject --exclude`
options allow excluding directories from the template.
Migrations Migrations
~~~~~~~~~~ ~~~~~~~~~~

View File

@ -2232,6 +2232,70 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
hidden_dir = os.path.join(testproject_dir, '.hidden') hidden_dir = os.path.join(testproject_dir, '.hidden')
self.assertIs(os.path.exists(hidden_dir), False) self.assertIs(os.path.exists(hidden_dir), False)
def test_custom_project_template_hidden_directory_included(self):
"""
Template context variables in hidden directories are rendered, if not
excluded.
"""
template_path = os.path.join(custom_templates_dir, 'project_template')
project_name = 'custom_project_template_hidden_directories_included'
args = [
'startproject',
'--template',
template_path,
project_name,
'project_dir',
'--exclude',
]
testproject_dir = os.path.join(self.test_dir, 'project_dir')
os.mkdir(testproject_dir)
_, err = self.run_django_admin(args)
self.assertNoOutput(err)
render_py_path = os.path.join(testproject_dir, '.hidden', 'render.py')
with open(render_py_path) as fp:
self.assertIn(
f'# The {project_name} should be rendered.',
fp.read(),
)
def test_custom_project_template_exclude_directory(self):
"""
Excluded directories (in addition to .git and __pycache__) are not
included in the project.
"""
template_path = os.path.join(custom_templates_dir, 'project_template')
project_name = 'custom_project_with_excluded_directories'
args = [
'startproject',
'--template',
template_path,
project_name,
'project_dir',
'--exclude',
'additional_dir',
'-x',
'.hidden',
]
testproject_dir = os.path.join(self.test_dir, 'project_dir')
os.mkdir(testproject_dir)
_, err = self.run_django_admin(args)
self.assertNoOutput(err)
excluded_directories = [
'.hidden',
'additional_dir',
'.git',
'__pycache__',
]
for directory in excluded_directories:
self.assertIs(
os.path.exists(os.path.join(testproject_dir, directory)),
False,
)
not_excluded = os.path.join(testproject_dir, project_name)
self.assertIs(os.path.exists(not_excluded), True)
class StartApp(AdminScriptTestCase): class StartApp(AdminScriptTestCase):