1
0
mirror of https://github.com/django/django.git synced 2025-07-22 10:39:13 +00:00

Fixed #18296 -- Created missing custom target directory for startproject and startapp.

This commit is contained in:
Bruno Alla 2025-04-27 12:01:24 +02:00 committed by Sarah Boyce
parent 3babda775d
commit bc21bc4282
5 changed files with 92 additions and 30 deletions

View File

@ -46,7 +46,9 @@ class TemplateCommand(BaseCommand):
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument("name", help="Name of the application or project.") parser.add_argument("name", help="Name of the application or project.")
parser.add_argument( parser.add_argument(
"directory", nargs="?", help="Optional destination directory" "directory",
nargs="?",
help="Optional destination directory, this will be created if needed.",
) )
parser.add_argument( parser.add_argument(
"--template", help="The path or URL to load the template from." "--template", help="The path or URL to load the template from."
@ -105,10 +107,10 @@ class TemplateCommand(BaseCommand):
if app_or_project == "app": if app_or_project == "app":
self.validate_name(os.path.basename(top_dir), "directory") self.validate_name(os.path.basename(top_dir), "directory")
if not os.path.exists(top_dir): if not os.path.exists(top_dir):
raise CommandError( try:
"Destination directory '%s' does not " os.makedirs(top_dir)
"exist, please create it first." % top_dir except OSError as e:
) raise CommandError(e)
# Find formatters, which are external executables, before input # Find formatters, which are external executables, before input
# from the templates can sneak into the path. # from the templates can sneak into the path.

View File

@ -45,21 +45,16 @@ including database configuration, Django-specific options and
application-specific settings. application-specific settings.
From the command line, ``cd`` into a directory where you'd like to store your From the command line, ``cd`` into a directory where you'd like to store your
code and create a new directory named ``djangotutorial``. (This directory name code and run the following command to bootstrap a new Django project:
doesn't matter to Django; you can rename it to anything you like.)
.. console::
$ mkdir djangotutorial
Then, run the following command to bootstrap a new Django project:
.. console:: .. console::
$ django-admin startproject mysite djangotutorial $ django-admin startproject mysite djangotutorial
This will create a project called ``mysite`` inside the ``djangotutorial`` This will create a directory ``djangotutorial`` with a project called
directory. If it didn't work, see :ref:`troubleshooting-django-admin`. ``mysite`` inside. The directory name doesn't matter to Django; you can rename
it to anything you like. If it didn't work, see
:ref:`troubleshooting-django-admin`.
.. note:: .. note::

View File

@ -1266,9 +1266,13 @@ By default, :source:`the new directory <django/conf/app_template>` contains a
``models.py`` file and other app template files. If only the app name is given, ``models.py`` file and other app template files. If only the app name is given,
the app directory will be created in the current working directory. the app directory will be created in the current working directory.
If the optional destination is provided, Django will use that existing If the optional destination is provided, Django will use that name instead. If
directory rather than creating a new one. You can use '.' to denote the current the directory with the given name doesn't exist, it will be created. You can
working directory. use '.' to denote the current working directory.
.. versionchanged:: 6.0
Automatic creation of the destination directory was added.
For example: For example:
@ -1378,9 +1382,14 @@ If only the project name is given, both the project directory and project
package will be named ``<projectname>`` and the project directory package will be named ``<projectname>`` and the project directory
will be created in the current working directory. will be created in the current working directory.
If the optional destination is provided, Django will use that existing If the optional destination is provided, Django will use that name as the
directory as the project directory, and create ``manage.py`` and the project project directory, and create ``manage.py`` and the project package within it.
package within it. Use '.' to denote the current working directory. If the directory with the given name doesn't exist, it will be created. Use '.'
to denote the current working directory.
.. versionchanged:: 6.0
Automatic creation of the destination directory was added.
For example: For example:

View File

@ -170,7 +170,8 @@ Logging
Management Commands Management Commands
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
* ... * The :djadmin:`startproject` and :djadmin:`startapp` commands now create the
custom target directory if it doesn't exist.
Migrations Migrations
~~~~~~~~~~ ~~~~~~~~~~

View File

@ -2843,8 +2843,7 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
def test_custom_project_destination_missing(self): def test_custom_project_destination_missing(self):
""" """
Make sure an exception is raised when the provided Create the directory when the provided destination directory doesn't exist.
destination directory doesn't exist
""" """
template_path = os.path.join(custom_templates_dir, "project_template") template_path = os.path.join(custom_templates_dir, "project_template")
args = [ args = [
@ -2857,12 +2856,8 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
testproject_dir = os.path.join(self.test_dir, "project_dir2") testproject_dir = os.path.join(self.test_dir, "project_dir2")
out, err = self.run_django_admin(args) out, err = self.run_django_admin(args)
self.assertNoOutput(out) self.assertNoOutput(out)
self.assertOutput( self.assertNoOutput(err)
err, self.assertTrue(os.path.exists(testproject_dir))
"Destination directory '%s' does not exist, please create it first."
% testproject_dir,
)
self.assertFalse(os.path.exists(testproject_dir))
def test_custom_project_template_with_non_ascii_templates(self): def test_custom_project_template_with_non_ascii_templates(self):
""" """
@ -3099,6 +3094,66 @@ class StartApp(AdminScriptTestCase):
content, content,
) )
def test_creates_directory_when_custom_app_destination_missing(self):
args = [
"startapp",
"my_app",
"my_app",
]
testapp_dir = os.path.join(self.test_dir, "my_app")
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertNoOutput(err)
self.assertTrue(os.path.exists(testapp_dir))
def test_custom_app_destination_missing_with_nested_subdirectory(self):
args = [
"startapp",
"my_app",
"apps/my_app",
]
testapp_dir = os.path.join(self.test_dir, "apps", "my_app")
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertNoOutput(err)
self.assertTrue(os.path.exists(testapp_dir))
def test_custom_name_with_app_within_other_app(self):
parent_app_dir = os.path.join(self.test_dir, "parent")
self.run_django_admin(["startapp", "parent"])
self.assertTrue(os.path.exists(parent_app_dir))
nested_args = ["startapp", "child", "parent/child"]
child_app_dir = os.path.join(self.test_dir, "parent", "child")
out, err = self.run_django_admin(nested_args)
self.assertNoOutput(out)
self.assertNoOutput(err)
self.assertTrue(os.path.exists(child_app_dir))
@unittest.skipIf(
sys.platform == "win32",
"Windows only partially supports umasks and chmod.",
)
def test_custom_app_directory_creation_error_handling(self):
"""The error is displayed to the user in case of OSError."""
args = [
"startapp",
"my_app",
"project_dir/my_app",
]
# Create a read-only parent directory.
os.makedirs(
os.path.join(self.test_dir, "project_dir"), exist_ok=True, mode=0o200
)
testapp_dir = os.path.join(self.test_dir, "project_dir", "my_app")
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(
err,
"Permission denied",
)
self.assertFalse(os.path.exists(testapp_dir))
class DiffSettings(AdminScriptTestCase): class DiffSettings(AdminScriptTestCase):
"""Tests for diffsettings management command.""" """Tests for diffsettings management command."""