Browse Source

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

sage 3 years ago
parent
commit
84c7c4a477

+ 17 - 1
django/core/management/templates.py

@@ -1,3 +1,4 @@
+import argparse
 import cgi
 import mimetypes
 import os
@@ -54,6 +55,14 @@ class TemplateCommand(BaseCommand):
             help='The file name(s) to render. Separate multiple file names '
                  '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):
         self.app_or_project = app_or_project
@@ -82,8 +91,12 @@ class TemplateCommand(BaseCommand):
 
         extensions = tuple(handle_extensions(options['extensions']))
         extra_files = []
+        excluded_directories = ['.git', '__pycache__']
         for file in options['files']:
             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:
             self.stdout.write(
                 'Rendering %s template files with extensions: %s'
@@ -126,7 +139,10 @@ class TemplateCommand(BaseCommand):
                 os.makedirs(target_dir, exist_ok=True)
 
             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)
 
             for filename in files:

+ 16 - 0
docs/ref/django-admin.txt

@@ -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
 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
 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
 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:
 
 - Any option passed to the ``startproject`` command (among the command's

+ 3 - 0
docs/releases/4.0.txt

@@ -278,6 +278,9 @@ Management Commands
   <django.core.management.BaseCommand.suppressed_base_arguments>` attribute
   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
 ~~~~~~~~~~
 

+ 64 - 0
tests/admin_scripts/tests.py

@@ -2232,6 +2232,70 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
         hidden_dir = os.path.join(testproject_dir, '.hidden')
         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):