Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 3 additions & 31 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,34 +1,6 @@
.envrc
.idea/
backend/
frontend/
django_mongodb_cli.egg-info/
__pycache__
manage.py
mongo_app/
mongo_project/
node_modules/
django_mongodb_cli/__pycache__/
/src/
.idea
server.log
server.pid
.babelrc
.browserslistrc
.eslintrc
.nvmrc
.stylelintrc.json
frontend/
package-lock.json
package.json
postcss.config.js
apps/
!test/apps/
src/
!project_templates/project_template/mongo_migrations
home/
.dockerignore
Dockerfile
search/
uv.lock
docs/_build/
config/wagtail/wagtail_settings.py
customer-master-key.txt
mongocryptd.pid
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@ repos:
- id: ruff
args: [ --fix ]
- id: ruff-format

- repo: https://github.com/rtts/djhtml
rev: '3.0.7'
hooks:
- id: djhtml
entry: djhtml --tabwidth 2
files: ./django_mongodb_cli/templates/project_template/project_name/templates/default_urlconf.html
6 changes: 6 additions & 0 deletions django_mongodb_cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import os
import typer

from .app import app
from .frontend import frontend
from .project import project
from .repo import repo

help_text = (
Expand All @@ -25,4 +28,7 @@ def main(ctx: typer.Context):
raise typer.Exit()


dm.add_typer(app, name="app")
dm.add_typer(frontend, name="frontend")
dm.add_typer(project, name="project")
dm.add_typer(repo, name="repo")
107 changes: 107 additions & 0 deletions django_mongodb_cli/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import typer
import shutil
from pathlib import Path
import subprocess
import importlib.resources as resources
import os

app = typer.Typer(help="Manage Django apps.")


@app.command("create")
def add_app(name: str, project_name: str, directory: Path = Path(".")):
"""
Create a new Django app inside an existing project using bundled templates.
"""
project_path = directory / project_name
if not project_path.exists() or not project_path.is_dir():
typer.echo(f"❌ Project '{project_name}' not found at {project_path}", err=True)
raise typer.Exit(code=1)

# Destination for new app
app_path = project_path / name
if app_path.exists():
typer.echo(
f"❌ App '{name}' already exists in project '{project_name}'", err=True
)
raise typer.Exit(code=1)

typer.echo(f"📦 Creating app '{name}' in project '{project_name}'")

# Locate the Django app template directory in package resources
with resources.path(
"django_mongodb_cli.templates", "app_template"
) as template_path:
cmd = [
"django-admin",
"startapp",
"--template",
str(template_path),
name,
str(project_path),
]
subprocess.run(cmd, check=True)


@app.command("remove")
def remove_app(name: str, project_name: str, directory: Path = Path(".")):
"""
Remove a Django app from a project.
"""
target = directory / project_name / name
if target.exists() and target.is_dir():
shutil.rmtree(target)
typer.echo(f"🗑️ Removed app '{name}' from project '{project_name}'")
else:
typer.echo(f"❌ App '{name}' does not exist in '{project_name}'", err=True)


def _django_admin_cmd(project_name: str, directory: Path, *args: str):
"""
Internal helper to run `django-admin` with project's DJANGO_SETTINGS_MODULE.
"""
project_path = directory / project_name
if not project_path.exists():
typer.echo(
f"❌ Project '{project_name}' does not exist at {project_path}", err=True
)
raise typer.Exit(code=1)

parent_dir = project_path.parent.resolve()

env = os.environ.copy()
env["DJANGO_SETTINGS_MODULE"] = f"{project_name}.settings"
env["PYTHONPATH"] = str(parent_dir) + os.pathsep + env.get("PYTHONPATH", "")

subprocess.run(["django-admin", *args], cwd=parent_dir, env=env, check=True)


@app.command("makemigrations")
def makemigrations_app(project_name: str, app_label: str, directory: Path = Path(".")):
"""
Run makemigrations for the given app inside a project.
"""
typer.echo(f"🛠️ Making migrations for app '{app_label}' in project '{project_name}'")
_django_admin_cmd(project_name, directory, "makemigrations", app_label)


@app.command("migrate")
def migrate_app(
project_name: str,
app_label: str = typer.Argument(None),
migration_name: str = typer.Argument(None),
directory: Path = Path("."),
):
"""
Apply migrations for the given app inside a project.
"""
cmd = ["migrate"]
if app_label:
cmd.append(app_label)
if migration_name:
cmd.append(migration_name)

typer.echo(
f"📦 Applying migrations for {app_label or 'all apps'} in '{project_name}'"
)
_django_admin_cmd(project_name, directory, *cmd)
181 changes: 181 additions & 0 deletions django_mongodb_cli/frontend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import typer
import shutil
from pathlib import Path
import subprocess
import importlib.resources as resources
import os

frontend = typer.Typer(help="Manage Django frontends.")


@frontend.command("create")
def add_frontend(
project_name: str,
directory: Path = Path("."),
):
"""
Create a new Django app inside an existing project using bundled templates.
"""
project_path = directory / project_name
name = "frontend"
if not project_path.exists() or not project_path.is_dir():
typer.echo(f"❌ Project '{project_name}' not found at {project_path}", err=True)
raise typer.Exit(code=1)
# Destination for new app
app_path = project_path / name
if app_path.exists():
typer.echo(
f"❌ App '{name}' already exists in project '{project_name}'", err=True
)
raise typer.Exit(code=1)
typer.echo(f"📦 Creating app '{name}' in project '{project_name}'")
# Locate the Django app template directory in package resources
with resources.path(
"django_mongodb_cli.templates", "frontend_template"
) as template_path:
cmd = [
"django-admin",
"startapp",
"--template",
str(template_path),
name,
directory,
]
subprocess.run(cmd, check=True)


@frontend.command("remove")
def remove_frontend(project_name: str, directory: Path = Path(".")):
"""
Remove a Django app from a project.
"""
name = "frontend"
target = directory / project_name / name
if target.exists() and target.is_dir():
shutil.rmtree(target)
typer.echo(f"🗑️ Removed app '{name}' from project '{project_name}'")
else:
typer.echo(f"❌ App '{name}' does not exist in '{project_name}'", err=True)


@frontend.command("install")
def install_npm(
project_name: str,
frontend_dir: str = "frontend",
directory: Path = Path("."),
clean: bool = typer.Option(
False,
"--clean",
help="Remove node_modules and package-lock.json before installing",
),
):
"""
Install npm dependencies in the frontend directory.
"""
project_path = directory / project_name
if not project_path.exists():
typer.echo(
f"❌ Project '{project_name}' does not exist at {project_path}", err=True
)
raise typer.Exit(code=1)

frontend_path = project_path / frontend_dir
if not frontend_path.exists():
typer.echo(
f"❌ Frontend directory '{frontend_dir}' not found at {frontend_path}",
err=True,
)
raise typer.Exit(code=1)

package_json = frontend_path / "package.json"
if not package_json.exists():
typer.echo(f"❌ package.json not found in {frontend_path}", err=True)
raise typer.Exit(code=1)

if clean:
typer.echo(f"🧹 Cleaning node_modules and package-lock.json in {frontend_path}")
node_modules = frontend_path / "node_modules"
package_lock = frontend_path / "package-lock.json"

if node_modules.exists():
shutil.rmtree(node_modules)
typer.echo(" ✓ Removed node_modules")

if package_lock.exists():
package_lock.unlink()
typer.echo(" ✓ Removed package-lock.json")

typer.echo(f"📦 Installing npm dependencies in {frontend_path}")

try:
subprocess.run(["npm", "install"], cwd=frontend_path, check=True)
typer.echo("✅ Dependencies installed successfully")
except subprocess.CalledProcessError as e:
typer.echo(f"❌ npm install failed with exit code {e.returncode}", err=True)
raise typer.Exit(code=e.returncode)
except FileNotFoundError:
typer.echo(
"❌ npm not found. Please ensure Node.js and npm are installed.", err=True
)
raise typer.Exit(code=1)


@frontend.command("run")
def run_npm(
project_name: str,
frontend_dir: str = "frontend",
directory: Path = Path("."),
script: str = typer.Option("watch", help="NPM script to run (default: watch)"),
):
"""
Run npm script in the frontend directory.
"""
project_path = directory / project_name
if not project_path.exists():
typer.echo(
f"❌ Project '{project_name}' does not exist at {project_path}", err=True
)
raise typer.Exit(code=1)

frontend_path = project_path / frontend_dir
if not frontend_path.exists():
typer.echo(
f"❌ Frontend directory '{frontend_dir}' not found at {frontend_path}",
err=True,
)
raise typer.Exit(code=1)

package_json = frontend_path / "package.json"
if not package_json.exists():
typer.echo(f"❌ package.json not found in {frontend_path}", err=True)
raise typer.Exit(code=1)

typer.echo(f"🚀 Running 'npm run {script}' in {frontend_path}")

try:
subprocess.run(["npm", "run", script], cwd=frontend_path, check=True)
except subprocess.CalledProcessError as e:
typer.echo(f"❌ npm command failed with exit code {e.returncode}", err=True)
raise typer.Exit(code=e.returncode)
except FileNotFoundError:
typer.echo(
"❌ npm not found. Please ensure Node.js and npm are installed.", err=True
)
raise typer.Exit(code=1)


def django_admin_cmd(project_name: str, directory: Path, *args: str):
"""
Internal helper to run `django-admin` with project's DJANGO_SETTINGS_MODULE.
"""
project_path = directory / project_name
if not project_path.exists():
typer.echo(
f"❌ Project '{project_name}' does not exist at {project_path}", err=True
)
raise typer.Exit(code=1)
parent_dir = project_path.parent.resolve()
env = os.environ.copy()
env["DJANGO_SETTINGS_MODULE"] = f"{project_name}.settings.base"
env["PYTHONPATH"] = str(parent_dir) + os.pathsep + env.get("PYTHONPATH", "")
subprocess.run(["django-admin", *args], cwd=parent_dir, env=env, check=True)
Loading