Skip to content

Commit c1c0cdf

Browse files
committed
Refactor with typer
- Update pyproject.toml - Add pymongo-search-utils - Add Faker - Add django-mongodb-demo - Update justfile - Update gitignore - Update .pre-commit-config.yaml - Add djhtml - Use `uv pip` instead of `pip` - Read test env vars from pyproject.toml - Add test/settings/qe.py for QE testing - Add env_vars to Django tests to set PYMONGOCRYPT_LIB for encryption_ tests - Add `dm repo fetch` command - Add `dm repo set-default` - Add `dm project` command - Add `dm project run` command - Add `dm project migrate` command - Add `dm project makemigrations` command - Add `dm project su` to create superuser - Get settings path from toml - Add custom MongoDB apps and migrations to project template - Add custom built-in welcome template at / - Add MONGODB_URI to env via `--mongodb-uri` - Move settings -> settings.base - Add `dm frontend` command - Add frontend template via python-webpack-boilerplate - Optionally add frontend when creating project - Optionally start the frontend when starting the project - Add jira dir - Add jira/PYTHON-5564.py - Move qe.py -> jira/
1 parent a5d17fb commit c1c0cdf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+13378
-66
lines changed

.gitignore

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,3 @@
1-
.envrc
2-
.idea/
3-
backend/
4-
frontend/
51
django_mongodb_cli.egg-info/
6-
__pycache__
7-
manage.py
8-
mongo_app/
9-
mongo_project/
10-
node_modules/
11-
server.log
12-
server.pid
13-
.babelrc
14-
.browserslistrc
15-
.eslintrc
16-
.nvmrc
17-
.stylelintrc.json
18-
frontend/
19-
package-lock.json
20-
package.json
21-
postcss.config.js
22-
apps/
23-
!test/apps/
24-
src/
25-
!project_templates/project_template/mongo_migrations
26-
home/
27-
.dockerignore
28-
Dockerfile
29-
search/
30-
uv.lock
31-
docs/_build/
32-
config/wagtail/wagtail_settings.py
33-
customer-master-key.txt
34-
mongocryptd.pid
2+
django_mongodb_cli/__pycache__/
3+
/src/

.pre-commit-config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,10 @@ repos:
1515
- id: ruff
1616
args: [ --fix ]
1717
- id: ruff-format
18+
19+
- repo: https://github.com/rtts/djhtml
20+
rev: '3.0.7'
21+
hooks:
22+
- id: djhtml
23+
entry: djhtml --tabwidth 2
24+
files: ./django_mongodb_cli/templates/project_template/project_name/templates/default_urlconf.html

django_mongodb_cli/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import os
22
import typer
33

4+
from .app import app
5+
from .frontend import frontend
6+
from .project import project
47
from .repo import repo
58

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

2730

31+
dm.add_typer(app, name="app")
32+
dm.add_typer(frontend, name="frontend")
33+
dm.add_typer(project, name="project")
2834
dm.add_typer(repo, name="repo")

django_mongodb_cli/app.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import typer
2+
import shutil
3+
from pathlib import Path
4+
import subprocess
5+
import importlib.resources as resources
6+
import os
7+
8+
app = typer.Typer(help="Manage Django apps.")
9+
10+
11+
@app.command("create")
12+
def add_app(name: str, project_name: str, directory: Path = Path(".")):
13+
"""
14+
Create a new Django app inside an existing project using bundled templates.
15+
"""
16+
project_path = directory / project_name
17+
if not project_path.exists() or not project_path.is_dir():
18+
typer.echo(f"❌ Project '{project_name}' not found at {project_path}", err=True)
19+
raise typer.Exit(code=1)
20+
21+
# Destination for new app
22+
app_path = project_path / name
23+
if app_path.exists():
24+
typer.echo(
25+
f"❌ App '{name}' already exists in project '{project_name}'", err=True
26+
)
27+
raise typer.Exit(code=1)
28+
29+
typer.echo(f"📦 Creating app '{name}' in project '{project_name}'")
30+
31+
# Locate the Django app template directory in package resources
32+
with resources.path(
33+
"django_mongodb_cli.templates", "app_template"
34+
) as template_path:
35+
cmd = [
36+
"django-admin",
37+
"startapp",
38+
"--template",
39+
str(template_path),
40+
name,
41+
str(project_path),
42+
]
43+
subprocess.run(cmd, check=True)
44+
45+
46+
@app.command("remove")
47+
def remove_app(name: str, project_name: str, directory: Path = Path(".")):
48+
"""
49+
Remove a Django app from a project.
50+
"""
51+
target = directory / project_name / name
52+
if target.exists() and target.is_dir():
53+
shutil.rmtree(target)
54+
typer.echo(f"🗑️ Removed app '{name}' from project '{project_name}'")
55+
else:
56+
typer.echo(f"❌ App '{name}' does not exist in '{project_name}'", err=True)
57+
58+
59+
def _django_admin_cmd(project_name: str, directory: Path, *args: str):
60+
"""
61+
Internal helper to run `django-admin` with project's DJANGO_SETTINGS_MODULE.
62+
"""
63+
project_path = directory / project_name
64+
if not project_path.exists():
65+
typer.echo(
66+
f"❌ Project '{project_name}' does not exist at {project_path}", err=True
67+
)
68+
raise typer.Exit(code=1)
69+
70+
parent_dir = project_path.parent.resolve()
71+
72+
env = os.environ.copy()
73+
env["DJANGO_SETTINGS_MODULE"] = f"{project_name}.settings"
74+
env["PYTHONPATH"] = str(parent_dir) + os.pathsep + env.get("PYTHONPATH", "")
75+
76+
subprocess.run(["django-admin", *args], cwd=parent_dir, env=env, check=True)
77+
78+
79+
@app.command("makemigrations")
80+
def makemigrations_app(project_name: str, app_label: str, directory: Path = Path(".")):
81+
"""
82+
Run makemigrations for the given app inside a project.
83+
"""
84+
typer.echo(f"🛠️ Making migrations for app '{app_label}' in project '{project_name}'")
85+
_django_admin_cmd(project_name, directory, "makemigrations", app_label)
86+
87+
88+
@app.command("migrate")
89+
def migrate_app(
90+
project_name: str,
91+
app_label: str = typer.Argument(None),
92+
migration_name: str = typer.Argument(None),
93+
directory: Path = Path("."),
94+
):
95+
"""
96+
Apply migrations for the given app inside a project.
97+
"""
98+
cmd = ["migrate"]
99+
if app_label:
100+
cmd.append(app_label)
101+
if migration_name:
102+
cmd.append(migration_name)
103+
104+
typer.echo(
105+
f"📦 Applying migrations for {app_label or 'all apps'} in '{project_name}'"
106+
)
107+
_django_admin_cmd(project_name, directory, *cmd)

django_mongodb_cli/frontend.py

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import typer
2+
import shutil
3+
from pathlib import Path
4+
import subprocess
5+
import importlib.resources as resources
6+
import os
7+
8+
frontend = typer.Typer(help="Manage Django apps.")
9+
10+
11+
@frontend.command("create")
12+
def add_frontend(
13+
project_name: str,
14+
directory: Path = Path("."),
15+
):
16+
"""
17+
Create a new Django app inside an existing project using bundled templates.
18+
"""
19+
project_path = directory / project_name
20+
name = "frontend"
21+
if not project_path.exists() or not project_path.is_dir():
22+
typer.echo(f"❌ Project '{project_name}' not found at {project_path}", err=True)
23+
raise typer.Exit(code=1)
24+
# Destination for new app
25+
app_path = project_path / name
26+
if app_path.exists():
27+
typer.echo(
28+
f"❌ App '{name}' already exists in project '{project_name}'", err=True
29+
)
30+
raise typer.Exit(code=1)
31+
typer.echo(f"📦 Creating app '{name}' in project '{project_name}'")
32+
# Locate the Django app template directory in package resources
33+
with resources.path(
34+
"django_mongodb_cli.templates", "frontend_template"
35+
) as template_path:
36+
cmd = [
37+
"django-admin",
38+
"startapp",
39+
"--template",
40+
str(template_path),
41+
name,
42+
directory,
43+
]
44+
subprocess.run(cmd, check=True)
45+
46+
47+
@frontend.command("remove")
48+
def remove_frontend(project_name: str, directory: Path = Path(".")):
49+
"""
50+
Remove a Django app from a project.
51+
"""
52+
name = "frontend"
53+
target = directory / project_name / name
54+
if target.exists() and target.is_dir():
55+
shutil.rmtree(target)
56+
typer.echo(f"🗑️ Removed app '{name}' from project '{project_name}'")
57+
else:
58+
typer.echo(f"❌ App '{name}' does not exist in '{project_name}'", err=True)
59+
60+
61+
@frontend.command("install")
62+
def install_npm(
63+
project_name: str,
64+
frontend_dir: str = "frontend",
65+
directory: Path = Path("."),
66+
clean: bool = typer.Option(
67+
False,
68+
"--clean",
69+
help="Remove node_modules and package-lock.json before installing",
70+
),
71+
):
72+
"""
73+
Install npm dependencies in the frontend directory.
74+
"""
75+
project_path = directory / project_name
76+
if not project_path.exists():
77+
typer.echo(
78+
f"❌ Project '{project_name}' does not exist at {project_path}", err=True
79+
)
80+
raise typer.Exit(code=1)
81+
82+
frontend_path = project_path / frontend_dir
83+
if not frontend_path.exists():
84+
typer.echo(
85+
f"❌ Frontend directory '{frontend_dir}' not found at {frontend_path}",
86+
err=True,
87+
)
88+
raise typer.Exit(code=1)
89+
90+
package_json = frontend_path / "package.json"
91+
if not package_json.exists():
92+
typer.echo(f"❌ package.json not found in {frontend_path}", err=True)
93+
raise typer.Exit(code=1)
94+
95+
if clean:
96+
typer.echo(f"🧹 Cleaning node_modules and package-lock.json in {frontend_path}")
97+
node_modules = frontend_path / "node_modules"
98+
package_lock = frontend_path / "package-lock.json"
99+
100+
if node_modules.exists():
101+
shutil.rmtree(node_modules)
102+
typer.echo(" ✓ Removed node_modules")
103+
104+
if package_lock.exists():
105+
package_lock.unlink()
106+
typer.echo(" ✓ Removed package-lock.json")
107+
108+
typer.echo(f"📦 Installing npm dependencies in {frontend_path}")
109+
110+
try:
111+
subprocess.run(["npm", "install"], cwd=frontend_path, check=True)
112+
typer.echo("✅ Dependencies installed successfully")
113+
except subprocess.CalledProcessError as e:
114+
typer.echo(f"❌ npm install failed with exit code {e.returncode}", err=True)
115+
raise typer.Exit(code=e.returncode)
116+
except FileNotFoundError:
117+
typer.echo(
118+
"❌ npm not found. Please ensure Node.js and npm are installed.", err=True
119+
)
120+
raise typer.Exit(code=1)
121+
122+
123+
@frontend.command("run")
124+
def run_npm(
125+
project_name: str,
126+
frontend_dir: str = "frontend",
127+
directory: Path = Path("."),
128+
script: str = typer.Option("watch", help="NPM script to run (default: watch)"),
129+
):
130+
"""
131+
Run npm script in the frontend directory.
132+
"""
133+
project_path = directory / project_name
134+
if not project_path.exists():
135+
typer.echo(
136+
f"❌ Project '{project_name}' does not exist at {project_path}", err=True
137+
)
138+
raise typer.Exit(code=1)
139+
140+
frontend_path = project_path / frontend_dir
141+
if not frontend_path.exists():
142+
typer.echo(
143+
f"❌ Frontend directory '{frontend_dir}' not found at {frontend_path}",
144+
err=True,
145+
)
146+
raise typer.Exit(code=1)
147+
148+
package_json = frontend_path / "package.json"
149+
if not package_json.exists():
150+
typer.echo(f"❌ package.json not found in {frontend_path}", err=True)
151+
raise typer.Exit(code=1)
152+
153+
typer.echo(f"🚀 Running 'npm run {script}' in {frontend_path}")
154+
155+
try:
156+
subprocess.run(["npm", "run", script], cwd=frontend_path, check=True)
157+
except subprocess.CalledProcessError as e:
158+
typer.echo(f"❌ npm command failed with exit code {e.returncode}", err=True)
159+
raise typer.Exit(code=e.returncode)
160+
except FileNotFoundError:
161+
typer.echo(
162+
"❌ npm not found. Please ensure Node.js and npm are installed.", err=True
163+
)
164+
raise typer.Exit(code=1)
165+
166+
167+
def django_admin_cmd(project_name: str, directory: Path, *args: str):
168+
"""
169+
Internal helper to run `django-admin` with project's DJANGO_SETTINGS_MODULE.
170+
"""
171+
project_path = directory / project_name
172+
if not project_path.exists():
173+
typer.echo(
174+
f"❌ Project '{project_name}' does not exist at {project_path}", err=True
175+
)
176+
raise typer.Exit(code=1)
177+
parent_dir = project_path.parent.resolve()
178+
env = os.environ.copy()
179+
env["DJANGO_SETTINGS_MODULE"] = f"{project_name}.settings.base"
180+
env["PYTHONPATH"] = str(parent_dir) + os.pathsep + env.get("PYTHONPATH", "")
181+
subprocess.run(["django-admin", *args], cwd=parent_dir, env=env, check=True)

0 commit comments

Comments
 (0)