Skip to content

Create Custom Django Management Command for Project + App SetupΒ #49

@lothartj

Description

@lothartj

Code of Conduct

  • I agree to follow Django's Code of Conduct

Feature Description

The current workflow for starting a new Django project requires multiple commands:

django-admin startproject myproject
cd myproject
python manage.py startapp myapp

Then manually adding the app to INSTALLED_APPS.

This patch adds a new create management command that combines these steps into a single command:

django-admin create myproject myapp

Features:

  • Creates both project and app in one command
  • Automatically adds app to INSTALLED_APPS
  • Maintains existing settings.py formatting
  • Includes comprehensive test coverage
  • Backwards compatible (existing commands remain unchanged)

Usage:

Basic usage

django-admin create myproject myapp

With optional directory

django-admin create myproject myapp --directory /path/to/directory

The command:

  1. Creates a new project using startproject
  2. Creates a new app using startapp
  3. Automatically adds the app to INSTALLED_APPS in settings.py
  4. Maintains proper indentation and quote style in settings.py

Test coverage includes:

  • Basic project and app creation
  • Directory handling
  • Error cases
  • Output messages
  • INSTALLED_APPS formatting

Patch includes:

  • New management command: django/core/management/commands/create.py
  • Test suite: django/tests/user_commands/tests.py

πŸ“¦ Patch Includes
New command module: django/core/management/commands/create.py

Associated test suite: django/tests/user_commands/tests.py

Problem

πŸ™‹ Problem Statement
The current multi-step project setup process can be repetitive and error-prone. Combining the commands and automating configuration of INSTALLED_APPS improves developer experience, especially for beginners or during rapid prototyping.

πŸ“ Additional Details
This patch maintains Django’s commitment to sensible defaults and developer ergonomics while offering a non-disruptive enhancement to project bootstrapping.

Let me know if you'd like a version of this in reStructuredText (reST) format for a Django ticket submission or if you need help writing the actual create.py command or tests.

import os
from pathlib import Path

from django.core.management import CommandError, call_command
from django.core.management.base import BaseCommand

class Command(BaseCommand):
help = (
"Creates a Django project and app in one command, automatically configuring "
"INSTALLED_APPS. Usage: django-admin create myproject myapp"
)
def add_arguments(self, parser):
parser.add_argument("project_name", help="Name of the Django project")
parser.add_argument("app_name", help="Name of the Django app")
parser.add_argument(
"--directory",
help="Optional directory for project creation",
default=None,
)
def handle(self, *args, **options):
project_name = options["project_name"]
app_name = options["app_name"]
target_dir = options["directory"]
if target_dir:
if not os.path.exists(target_dir):
os.makedirs(target_dir)
os.chdir(target_dir)
self.stdout.write(f"Creating project '{project_name}'...")
try:
call_command("startproject", project_name, ".")
except Exception as e:
raise CommandError(f"Failed to create project: {e}")
self.stdout.write(f"Creating app '{app_name}'...")
os.chdir(project_name)
try:
call_command("startapp", app_name)
except Exception as e:
raise CommandError(f"Failed to create app: {e}")
settings_path = Path(project_name) / "settings.py"
if not settings_path.exists():
raise CommandError("Could not find settings.py")
self.stdout.write("Updating INSTALLED_APPS...")
with open(settings_path) as f:
content = f.read()
apps_start = content.find("INSTALLED_APPS")
if apps_start == -1:
raise CommandError("Could not find INSTALLED_APPS in settings.py")
bracket_start = content.find("[", apps_start)
if bracket_start == -1:
raise CommandError("Could not parse INSTALLED_APPS format")
quote_style = "'" if content[bracket_start:].find("'") < content[bracket_start:].find('"') else '"'
bracket_end = content.find("]", bracket_start)
if bracket_end == -1:
raise CommandError("Could not parse INSTALLED_APPS format")
last_newline = content.rfind("\n", 0, bracket_start)
indentation = " " * (bracket_start - last_newline - 1)
new_app = f"{quote_style}{project_name}.{app_name}{quote_style},"
apps_list = content[bracket_start + 1:bracket_end].strip()
if apps_list:
new_content = (
content[:bracket_end].rstrip()
+ "\n"
+ indentation
+ new_app
+ "\n"
+ indentation[:-4]
+ "]"
+ content[bracket_end + 1:]
)
else:
new_content = (
content[:bracket_start + 1]
+ "\n"
+ indentation
+ new_app
+ "\n"
+ indentation[:-4]
+ "]"
+ content[bracket_end + 1:]
)
with open(settings_path, "w") as f:
f.write(new_content)
self.stdout.write(
self.style.SUCCESS(
f"Successfully created project '{project_name}' and app '{app_name}'"
)
)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions