WIP: new updates #48

Closed
S4NKALP wants to merge 32 commits from dev into main
pull from: dev
38 changed files with 2695 additions and 97 deletions

No files matched your search

+14 -24
View File
@@ -65,7 +65,9 @@ The wizard will ask you a few questions:
4. **Django Apps** *(Standard structure only)* – Whether to create an `apps/` folder and which apps to scaffold.
5. **CI/CD Pipeline** – GitHub Actions, GitLab CI, both, or skip it.
5. **Optional Features** – Docker, Tailwind, HTMX, Vite (React/Vue), pytest, Django-Q2, Celery
6. **CI/CD Pipeline** – GitHub Actions, GitLab CI, both, or skip it.
That’s it—your project will be ready with everything configured.
@@ -76,8 +78,12 @@ That’s it—your project will be ready with everything configured.
- **API documentation** (Swagger UI at `/docs/`)
- **CORS** configured for local development
- **WhiteNoise** for static files
- **PostgreSQL** support (SQLite for dev)
- **PostgreSQL** support (MySQL also available)
- **Modern admin** interface (`django-jazzmin`)
- **Docker support** (Dockerfile, docker-compose.yml)
- **Frontend integration** (Vite + React or Vue.js via django-vite)
- **Optional testing** (pytest, pytest-django, pytest-cov)
- **Optional async tasks** (Django-Q2 or Celery with Redis)
- **Deployment ready** with `Procfile` and `runtime.txt`
- **Development tools** (`Justfile` with common commands)
- **Environment template** (`.env.sample`)
@@ -198,9 +204,14 @@ If you don’t have `just` installed, these are just shortcuts for the equivalen
- **django‑cors‑headers** – CORS handling
- **django‑jazzmin** – Modern admin UI
- **whitenoise** – Static file serving
- **psycopg2‑binary** – PostgreSQL driver
- **psycopg** – PostgreSQL/MySQL driver
- **gunicorn** – Production WSGI server
- **python‑dotenv** – `.env` handling
- **django‑vite** – Vite integration (optional)
- **django‑tailwind‑cli** – Tailwind CSS (optional)
- **django‑htmx** – HTMX integration (optional)
- **django‑q2** – Async tasks (optional)
- **celery** – Async tasks with Redis (optional)
### API Endpoints
@@ -233,27 +244,6 @@ ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
SQLite works out of the box for development—no extra DB setup required.
## Roadmap
The following items are tracked in **TODO.md** and represent the near‑future direction of djinit.
### Planned Features
- **Docker Support** – Auto‑generate a `Dockerfile` for containerized deployments.
- **Frontend Integration** – Scaffold React, Vue, or HTMX alongside the Django backend.
- **Celery Integration** – Simplify background task setup with Celery.
- **More Packages** – Add optional integrations for popular Django packages.
### Enhancements
- **Add more project structure templates** – Expand the set of ready‑made layouts.
- **Add testing framework setup** – Provide pytest and coverage configuration out of the box.
- **Fix bugs** – Ongoing maintenance and bug resolution.
### Completed
- **Interactive configuration wizard** – Streamlined project creation experience.
- **Improved structure detection** – Smarter detection of existing Django layouts.
> Contributions that address any of the above items are highly welcome!
## Contributing
Found a bug or have an idea? Open an issue or submit a pull request. Contributions are always welcome!
+23 -6
View File
@@ -2,15 +2,32 @@
## Features
- [ ] Docker Support: Auto generating `Dockerfile` so you can containerize easily
- [ ] Frontend Integration: Options to scaffold React, Vue, or HTMX right alongside Django
- [ ] Celery: Making background tasks easier to set up
- [x] Docker Support: Auto generating `Dockerfile`, `docker-compose.yml`, `.dockerignore`
- [x] Frontend Integration: Vite + React + Vue with django-vite (frontend/, vite.config.js, package.json)
- [x] Tailwind CSS: Integrated via django-tailwind-cli
- [x] HTMX: Integrated via django-htmx
- [x] Testing Framework: pytest, pytest-django, pytest-cov
- [x] Django-Q2: Optional async task queue (default: disabled)
- [x] Celery: Optional async task queue with Redis broker (default: disabled)
- [ ] More Packages: Integrating other popular packages to help you build feature rich apps effortlessly
## Enhancements
- [ ] Add more project structure templates
- [x] Add more project structure templates (Standard, Predefined, Unified, Single)
- [x] Add interactive configuration wizard
- [ ] Add testing framework setup (pytest, coverage)
- [x] Add testing framework setup (pytest, pytest-django, pytest-cov)
- [x] Improve structure detection
- [ ] Fix bugs
- [x] Fix bugs (Verified: All tests passing, no issues found)
## Project Structures
- [x] Standard Structure: `config/` module, `apps/` directory with nested apps
- [x] Predefined Structure: `apps/users`, `apps/core`, `api/` with v1
- [x] Unified Structure: `core/` as project config, `apps/` as main app
- [x] Single Folder Layout: Everything in one project folder
## Database Support
- [x] PostgreSQL
- [x] MySQL
- [x] DATABASE_URL support
+250
View File
@@ -0,0 +1,250 @@
#!/usr/bin/env python3
"""
Bug detection and checking script for djinit.
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from djinit.templater import template_engine # noqa: E402
def check_template_consistency():
"""Check if all templates have consistent variable syntax."""
print("=" * 70)
print(" CHECKING TEMPLATE CONSISTENCY")
print("=" * 70)
templates = [
"project/Dockerfile-tpl",
"project/docker-compose.yml-tpl",
"project/vite.config.js-tpl",
"project/package.json-tpl",
"project/requirements-tpl",
"config/settings/base.py-tpl",
]
issues = []
for tpl in templates:
try:
# Just try to render with empty context to see if it parses
# Using [[ var ]] syntax
content = template_engine.render_template(tpl, {"project_name": "test", "module_name": "config"})
# Check for any [[ that weren't replaced
if "[[" in content or "]]" in content:
issues.append(f"Template {tpl} has unreplaced [[ ]] variables")
except Exception as e:
issues.append(f"Template {tpl} error: {e}")
return issues
def check_metadata_usage():
"""Check if all metadata fields are used in templates."""
print("\n" + "=" * 70)
print(" CHECKING METADATA USAGE")
print("=" * 70)
metadata_fields = [
"use_docker",
"use_vite",
"use_tailwind",
"use_htmx",
"use_database_url",
"database_type",
]
# Check requirements template
req_template = "project/requirements-tpl"
req_content = open(os.path.join(os.path.dirname(__file__), "..", "src/djinit/templates", req_template)).read()
issues = []
for field in metadata_fields:
if f"use_{field}" in str(metadata_fields) or field in req_content:
# Check if the field is used in template
if field not in req_content and field != "use_database_url":
# Check conditional usage
if f"@IF {field}" not in req_content and f"@IF use_{field}" not in req_content:
pass # It's optional
return issues
def check_all_use_cases():
"""Test all combinations to find issues."""
print("\n" + "=" * 70)
print(" CHECKING ALL USE CASES")
print("=" * 70)
use_cases = [
{"name": "basic", "metadata": {}},
{"name": "docker", "metadata": {"use_docker": True, "database_type": "postgresql"}},
{"name": "vite", "metadata": {"use_vite": True}},
{"name": "tailwind", "metadata": {"use_tailwind": True}},
{"name": "htmx", "metadata": {"use_htmx": True}},
{"name": "docker_mysql", "metadata": {"use_docker": True, "database_type": "mysql"}},
{
"name": "all",
"metadata": {
"use_docker": True,
"use_vite": True,
"use_tailwind": True,
"use_htmx": True,
"database_type": "postgresql",
},
},
]
issues = []
for case in use_cases:
name = case["name"]
md = case["metadata"]
ctx = {"project_name": "test", "module_name": "config", "use_database_url": True, **md}
# Check requirements
try:
req = template_engine.render_template("project/requirements-tpl", ctx)
# Check for django-vite when use_vite is True
if md.get("use_vite") and "django-vite" not in req:
issues.append(f"{name}: django-vite missing in requirements")
# Check for django-tailwind when use_tailwind is True
if md.get("use_tailwind") and "django-tailwind-cli" not in req:
issues.append(f"{name}: django-tailwind-cli missing in requirements")
# Check for django-htmx when use_htmx is True
if md.get("use_htmx") and "django-htmx" not in req:
issues.append(f"{name}: django-htmx missing in requirements")
# Check for mysqlclient when MySQL
if md.get("database_type") == "mysql" and "mysqlclient" not in req:
issues.append(f"{name}: mysqlclient missing in requirements")
# Check for psycopg when PostgreSQL
if md.get("database_type") != "mysql" and "psycopg" not in req:
issues.append(f"{name}: psycopg missing in requirements")
except Exception as e:
issues.append(f"{name}: requirements error - {e}")
# Check Dockerfile for MySQL client
if md.get("database_type") == "mysql" and md.get("use_docker"):
try:
dockerfile = template_engine.render_template("project/Dockerfile-tpl", ctx)
if "default-libmysqlclient-dev" not in dockerfile and "mysqlclient" not in dockerfile:
issues.append(f"{name}: MySQL client missing in Dockerfile")
except Exception as e:
issues.append(f"{name}: Dockerfile error - {e}")
return issues
def check_settings():
"""Check settings templates for correct conditional includes."""
print("\n" + "=" * 70)
print(" CHECKING SETTINGS")
print("=" * 70)
issues = []
test_cases = [
{"use_vite": True, "use_tailwind": False, "use_htmx": False},
{"use_vite": False, "use_tailwind": True, "use_htmx": False},
{"use_vite": False, "use_tailwind": False, "use_htmx": True},
{"use_vite": True, "use_tailwind": True, "use_htmx": True},
]
for ctx in test_cases:
full_ctx = {"project_name": "config", "app_names": [], **ctx}
try:
settings = template_engine.render_template("config/settings/base.py-tpl", full_ctx)
# Check django_vite
if ctx.get("use_vite") and "django_vite" not in settings:
issues.append(f"settings with {ctx}: django_vite missing")
if ctx.get("use_vite") and "DJANGO_VITE" not in settings:
issues.append(f"settings with {ctx}: DJANGO_VITE config missing")
# Check django_tailwind
if ctx.get("use_tailwind") and "django_tailwind_cli" not in settings:
issues.append(f"settings with {ctx}: django_tailwind_cli missing")
# Check django_htmx
if ctx.get("use_htmx") and "django_htmx" not in settings:
issues.append(f"settings with {ctx}: django_htmx missing")
except Exception as e:
issues.append(f"settings error with {ctx}: {e}")
return issues
def check_vite_templates():
"""Check vite/frontend templates."""
print("\n" + "=" * 70)
print(" CHECKING VITE/FRONTEND TEMPLATES")
print("=" * 70)
ctx = {"project_name": "test", "module_name": "config"}
templates = [
"project/vite.config.js-tpl",
"project/package.json-tpl",
"project/frontend/index.html-tpl",
"project/frontend/src/main.jsx-tpl",
"project/frontend/src/App.jsx-tpl",
"project/frontend/src/index.css-tpl",
]
issues = []
for tpl in templates:
try:
content = template_engine.render_template(tpl, ctx)
# Check for unreplaced variables
if "[[" in content:
issues.append(f"{tpl}: has unreplaced [[ variables")
except Exception as e:
issues.append(f"{tpl}: error - {e}")
return issues
def main():
print("DJINIT BUG CHECK")
print("=" * 70)
all_issues = []
# Run all checks
all_issues.extend(check_template_consistency())
all_issues.extend(check_metadata_usage())
all_issues.extend(check_all_use_cases())
all_issues.extend(check_settings())
all_issues.extend(check_vite_templates())
# Report
print("\n" + "=" * 70)
print(" BUG CHECK SUMMARY")
print("=" * 70)
if all_issues:
print(f"\nFound {len(all_issues)} issues:")
for issue in all_issues:
print(f" ✗ {issue}")
else:
print("\n ✓ No issues found!")
print("\n" + "=" * 70)
print(" COMPLETED")
print("=" * 70)
if __name__ == "__main__":
main()
+201
View File
@@ -0,0 +1,201 @@
#!/usr/bin/env python3
"""
Comprehensive test for all djinit features.
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from djinit.creators.setup import SetupCreator # noqa: E402
def create_project(name, metadata):
"""Create a test project."""
test_dir = "/tmp/djinit_full_test"
os.makedirs(test_dir, exist_ok=True)
original_cwd = os.getcwd()
try:
os.chdir(test_dir)
# Set defaults
full_metadata = {
"package_name": "backend",
"use_github_actions": False,
"use_gitlab_ci": False,
"nested_apps": True,
"nested_dir": "apps",
"use_database_url": True,
"database_type": "postgresql",
"use_tailwind": False,
"use_htmx": False,
"use_docker": False,
"use_vite": False,
"predefined_structure": False,
"unified_structure": False,
"single_structure": False,
"project_module_name": "config",
**metadata,
}
creator = SetupCreator(
project_dir=name, project_name=name, primary_app="users", app_names=["users"], metadata=full_metadata
)
success = creator.create()
return success, test_dir
except Exception as e:
print(f" Error: {e}")
return False, test_dir
finally:
os.chdir(original_cwd)
def verify_file(path, content_contains=None):
"""Verify a file exists and optionally check content."""
if not os.path.exists(path):
return False, f"File not found: {path}"
if content_contains:
with open(path) as f:
content = f.read()
if content_contains not in content:
return False, f"Content '{content_contains}' not found in {path}"
return True, "OK"
def run_tests():
print("=" * 70)
print(" COMPREHENSIVE DJINIT TESTS")
print("=" * 70)
test_cases = [
("basic", {}, ["requirements.txt"]),
("docker", {"use_docker": True}, ["Dockerfile", "docker-compose.yml"]),
("vite", {"use_vite": True}, ["vite.config.js", "package.json", "frontend/index.html"]),
("tailwind", {"use_tailwind": True}, ["requirements.txt"]),
("htmx", {"use_htmx": True}, ["requirements.txt"]),
(
"all_postgres",
{
"use_docker": True,
"use_vite": True,
"use_tailwind": True,
"use_htmx": True,
"database_type": "postgresql",
},
["Dockerfile", "docker-compose.yml", "vite.config.js"],
),
(
"all_mysql",
{"use_docker": True, "use_vite": True, "use_tailwind": True, "database_type": "mysql"},
["Dockerfile", "docker-compose.yml", "vite.config.js"],
),
]
results = []
for name, metadata, expected_files in test_cases:
print(f"\n{'─' * 70}")
print(f" Testing: {name}")
print(f" Metadata: {metadata}")
# Clean up
import shutil
test_dir = "/tmp/djinit_full_test"
if os.path.exists(test_dir):
shutil.rmtree(test_dir)
# Create project
success, _ = create_project(name, metadata)
if not success:
results.append((name, False, "Project creation failed"))
print(" ✗ Failed to create project")
continue
# Verify files
project_dir = os.path.join(test_dir, name)
all_passed = True
errors = []
for file in expected_files:
file_path = os.path.join(project_dir, file)
exists, msg = verify_file(file_path)
if not exists:
all_passed = False
errors.append(msg)
print(f" ✗ {msg}")
else:
print(f" ✓ {file}")
# Special checks for vite
if metadata.get("use_vite"):
settings_path = os.path.join(project_dir, "config/settings/base.py")
exists, msg = verify_file(settings_path, "django_vite")
if not exists:
all_passed = False
errors.append("django_vite not in settings")
print(" ✗ django_vite not in settings")
else:
print(" ✓ django_vite in settings")
req_path = os.path.join(project_dir, "requirements.txt")
exists, msg = verify_file(req_path, "django-vite")
if not exists:
all_passed = False
errors.append("django-vite not in requirements")
print(" ✗ django-vite not in requirements")
else:
print(" ✓ django-vite in requirements")
# Special checks for docker
if metadata.get("use_docker"):
dockerfile_path = os.path.join(project_dir, "Dockerfile")
db_type = metadata.get("database_type", "postgresql")
if db_type == "mysql":
exists, msg = verify_file(dockerfile_path, "default-libmysqlclient-dev")
if not exists:
all_passed = False
print(" ✗ MySQL client not in Dockerfile")
else:
print(" ✓ MySQL client in Dockerfile")
else:
exists, msg = verify_file(dockerfile_path, "libpq-dev")
if not exists:
all_passed = False
print(" ✗ PostgreSQL client not in Dockerfile")
else:
print(" ✓ PostgreSQL client in Dockerfile")
if all_passed:
results.append((name, True, "All checks passed"))
print(" ✓ ALL CHECKS PASSED")
else:
results.append((name, False, "; ".join(errors)))
print(" ✗ SOME CHECKS FAILED")
# Summary
print("\n" + "=" * 70)
print(" SUMMARY")
print("=" * 70)
passed = sum(1 for _, s, _ in results if s)
total = len(results)
for name, success, msg in results:
status = "✓ PASS" if success else "✗ FAIL"
print(f" {status}: {name} - {msg}")
print(f"\n Total: {passed}/{total} passed")
if passed == total:
print("\n 🎉 ALL TESTS PASSED!")
else:
print("\n ⚠️ SOME TESTS FAILED!")
if __name__ == "__main__":
run_tests()
+189
View File
@@ -0,0 +1,189 @@
#!/usr/bin/env python3
"""
Generate real test projects to verify djinit works correctly.
Run from djinit project root: .venv/bin/python examples/generate_projects.py
"""
import os
import sys
# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from djinit.creators.setup import SetupCreator # noqa: E402
def create_test_project(name, structure, metadata, use_github=False, use_gitlab=False):
"""Create a test project."""
# Create temp directory
test_dir = os.path.join("/tmp/djinit_tests", name)
os.makedirs(test_dir, exist_ok=True)
original_cwd = os.getcwd()
try:
os.chdir(test_dir)
# Update metadata with defaults
full_metadata = {
"package_name": "backend",
"use_github_actions": use_github,
"use_gitlab_ci": use_gitlab,
"nested_apps": True,
"nested_dir": "apps",
"use_database_url": True,
"database_type": "postgresql",
"use_tailwind": False,
"use_htmx": False,
"use_docker": False,
"use_vite": False,
"predefined_structure": False,
"unified_structure": False,
"single_structure": False,
"project_module_name": "config",
**metadata,
}
# Determine structure type
if structure == "standard":
full_metadata["project_module_name"] = "config"
app_names = ["users"]
elif structure == "predefined":
full_metadata["predefined_structure"] = True
full_metadata["project_module_name"] = "config"
full_metadata["nested_apps"] = True
full_metadata["nested_dir"] = "apps"
app_names = ["users", "core"]
elif structure == "unified":
full_metadata["unified_structure"] = True
full_metadata["project_module_name"] = "core"
full_metadata["nested_apps"] = True
full_metadata["nested_dir"] = "apps"
app_names = []
elif structure == "single":
full_metadata["single_structure"] = True
full_metadata["project_module_name"] = name
full_metadata["nested_apps"] = False
full_metadata["nested_dir"] = None
app_names = []
# Create project
creator = SetupCreator(
project_dir=name,
project_name=name,
primary_app=app_names[0] if app_names else "",
app_names=app_names,
metadata=full_metadata,
)
success = creator.create()
if success:
print(f" ✓ Created: {test_dir}")
return True
else:
print(f" ✗ Failed: {test_dir}")
return False
except Exception as e:
print(f" ✗ Error creating {name}: {e}")
import traceback
traceback.print_exc()
return False
finally:
os.chdir(original_cwd)
def list_project_files(project_dir):
"""List key files in the project."""
print("\n Key files:")
key_files = [
"requirements.txt",
"Dockerfile",
"docker-compose.yml",
"package.json",
"vite.config.js",
"settings/base.py",
"manage.py",
]
for root, dirs, files in os.walk(project_dir):
# Skip hidden and common dirs
dirs[:] = [d for d in dirs if not d.startswith(".") and d not in ("__pycache__", "node_modules")]
for f in files:
if f in key_files:
rel_path = os.path.relpath(os.path.join(root, f), project_dir)
print(f" - {rel_path}")
def main():
print("=" * 70)
print(" DJINIT - GENERATE REAL TEST PROJECTS")
print("=" * 70)
# Create test directory
os.makedirs("/tmp/djinit_tests", exist_ok=True)
# Define test projects
test_projects = [
# Structure: (name, structure, metadata)
("test_standard_basic", "standard", {}),
("test_standard_docker", "standard", {"use_docker": True}),
("test_standard_vite", "standard", {"use_vite": True}),
(
"test_standard_all",
"standard",
{"use_docker": True, "use_vite": True, "use_tailwind": True, "use_htmx": True},
),
("test_predefined_basic", "predefined", {}),
("test_predefined_docker", "predefined", {"use_docker": True}),
("test_unified_basic", "unified", {}),
("test_unified_all", "unified", {"use_docker": True, "use_vite": True, "use_tailwind": True}),
("test_single_basic", "single", {}),
("test_single_docker", "single", {"use_docker": True}),
]
results = []
for name, structure, metadata in test_projects:
print(f"\n{'─' * 70}")
print(f" Creating: {name}")
print(f" Structure: {structure}")
print(f" Features: {list(metadata.keys()) if metadata else ['basic']}")
success = create_test_project(name, structure, metadata)
results.append((name, success))
if success:
list_project_files(f"/tmp/djinit_tests/{name}")
# Summary
print("\n" + "=" * 70)
print(" SUMMARY")
print("=" * 70)
passed = sum(1 for _, s in results if s)
total = len(results)
for name, success in results:
status = "✓ PASS" if success else "✗ FAIL"
print(f" {status}: {name}")
print(f"\n Total: {passed}/{total} passed")
if passed == total:
print("\n 🎉 All tests passed!")
else:
print("\n ⚠️ Some tests failed!")
# Show created directories
print("\n" + "=" * 70)
print(" CREATED PROJECTS")
print("=" * 70)
for name, _ in results:
print(f" /tmp/djinit_tests/{name}")
if __name__ == "__main__":
main()
+357
View File
@@ -0,0 +1,357 @@
#!/usr/bin/env python3
"""
Comprehensive test for ALL djinit features and use cases.
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) # noqa: E402
import shutil # noqa: E402
from djinit.creators.setup import SetupCreator # noqa: E402
def create_project(name, metadata, structure="standard"):
test_dir = "/tmp/djinit_full_test"
os.makedirs(test_dir, exist_ok=True)
original_cwd = os.getcwd()
try:
os.chdir(test_dir)
full_metadata = {
"package_name": "backend",
"use_github_actions": False,
"use_gitlab_ci": False,
"nested_apps": True,
"nested_dir": "apps",
"use_database_url": True,
"database_type": "postgresql",
"use_tailwind": False,
"use_htmx": False,
"use_docker": False,
"use_vite": False,
"use_vue": False,
"use_pytest": False,
"predefined_structure": False,
"unified_structure": False,
"single_structure": False,
"project_module_name": "config",
**metadata,
}
# Handle different structures
if structure == "predefined":
full_metadata["predefined_structure"] = True
full_metadata["project_module_name"] = "config"
full_metadata["nested_apps"] = True
full_metadata["nested_dir"] = "apps"
app_names = ["users", "core"]
elif structure == "unified":
full_metadata["unified_structure"] = True
full_metadata["project_module_name"] = "core"
full_metadata["nested_apps"] = True
full_metadata["nested_dir"] = "apps"
app_names = []
elif structure == "single":
full_metadata["single_structure"] = True
full_metadata["project_module_name"] = name
full_metadata["nested_apps"] = False
full_metadata["nested_dir"] = None
app_names = []
else: # standard
full_metadata["project_module_name"] = "config"
app_names = ["users"]
creator = SetupCreator(
project_dir=name,
project_name=name,
primary_app=app_names[0] if app_names else "users",
app_names=app_names,
metadata=full_metadata,
)
success = creator.create()
return success, test_dir
except Exception as e:
print(f" Error: {e}")
import traceback
traceback.print_exc()
return False, test_dir
finally:
os.chdir(original_cwd)
def verify_file(path, content_contains=None):
if not os.path.exists(path):
return False, f"File not found: {path}"
if content_contains:
with open(path) as f:
content = f.read()
if content_contains not in content:
return False, f"Content '{content_contains}' not found in {path}"
return True, "OK"
def run_all_tests():
print("=" * 70)
print(" DJINIT - COMPLETE TEST SUITE")
print("=" * 70)
# All test cases: (name, structure, metadata, expected_files, extra_checks)
test_cases = [
# Basic tests
("basic_std", "standard", {}, ["requirements.txt"], []),
("basic_pre", "predefined", {}, ["requirements.txt"], []),
("basic_uni", "unified", {}, ["requirements.txt"], []),
("basic_sgl", "single", {}, ["requirements.txt"], []),
# Single features
(
"docker",
"standard",
{"use_docker": True, "database_type": "postgresql"},
["Dockerfile", "docker-compose.yml"],
["postgres"],
),
(
"docker_mysql",
"standard",
{"use_docker": True, "database_type": "mysql"},
["Dockerfile", "docker-compose.yml"],
["mysql"],
),
("react", "standard", {"use_vite": True}, ["vite.config.js", "package.json"], ["react"]),
("vue", "standard", {"use_vue": True}, ["vite.config.js", "package.json"], ["vue"]),
("tailwind", "standard", {"use_tailwind": True}, ["requirements.txt"], ["tailwind"]),
("htmx", "standard", {"use_htmx": True}, ["requirements.txt"], ["htmx"]),
("pytest", "standard", {"use_pytest": True}, ["pytest.ini", "conftest.py"], ["pytest"]),
# Combinations
(
"docker_react",
"standard",
{"use_docker": True, "use_vite": True, "database_type": "postgresql"},
["Dockerfile", "vite.config.js"],
["postgres", "react"],
),
(
"docker_vue",
"standard",
{"use_docker": True, "use_vue": True, "database_type": "postgresql"},
["Dockerfile", "vite.config.js"],
["postgres", "vue"],
),
(
"react_pytest",
"standard",
{"use_vite": True, "use_pytest": True},
["vite.config.js", "pytest.ini"],
["react", "pytest"],
),
(
"vue_pytest",
"standard",
{"use_vue": True, "use_pytest": True},
["vite.config.js", "pytest.ini"],
["vue", "pytest"],
),
(
"tailwind_htmx",
"standard",
{"use_tailwind": True, "use_htmx": True},
["requirements.txt"],
["tailwind", "htmx"],
),
# All features
(
"all_postgres",
"standard",
{
"use_docker": True,
"use_vite": True,
"use_tailwind": True,
"use_htmx": True,
"use_pytest": True,
"database_type": "postgresql",
},
["Dockerfile", "vite.config.js", "pytest.ini"],
["postgres", "react", "pytest"],
),
(
"all_mysql",
"standard",
{"use_docker": True, "use_vue": True, "use_tailwind": True, "use_pytest": True, "database_type": "mysql"},
["Dockerfile", "vite.config.js", "pytest.ini"],
["mysql", "vue", "pytest"],
),
# Structure tests with features
(
"predefined_docker",
"predefined",
{"use_docker": True, "database_type": "postgresql"},
["Dockerfile"],
["postgres"],
),
("predefined_vue", "predefined", {"use_vue": True}, ["vite.config.js"], ["vue"]),
(
"unified_docker",
"unified",
{"use_docker": True, "database_type": "postgresql"},
["Dockerfile"],
["postgres"],
),
("unified_vue", "unified", {"use_vue": True}, ["vite.config.js"], ["vue"]),
("single_docker", "single", {"use_docker": True, "database_type": "postgresql"}, ["Dockerfile"], ["postgres"]),
("single_vue", "single", {"use_vue": True}, ["vite.config.js"], ["vue"]),
]
results = []
for name, structure, metadata, expected_files, extra_checks in test_cases:
print(f"\n{'─' * 70}")
print(f" Testing: {name} (structure: {structure})")
print(f" Features: {list(metadata.keys()) if metadata else ['basic']}")
test_dir = "/tmp/djinit_full_test"
if os.path.exists(test_dir):
shutil.rmtree(test_dir)
success, _ = create_project(name, metadata, structure)
if not success:
results.append((name, False, "Project creation failed"))
print(" ✗ Failed to create project")
continue
project_dir = os.path.join(test_dir, name)
all_passed = True
errors = []
# Check expected files
for file in expected_files:
file_path = os.path.join(project_dir, file)
exists, msg = verify_file(file_path)
if not exists:
all_passed = False
errors.append(msg)
print(f" ✗ {msg}")
else:
print(f" ✓ {file}")
# Run extra checks
for check in extra_checks:
if check == "postgres":
dockerfile_path = os.path.join(project_dir, "Dockerfile")
exists, _ = verify_file(dockerfile_path, "libpq-dev")
if not exists:
all_passed = False
print(" ✗ PostgreSQL client missing in Dockerfile")
else:
print(" ✓ PostgreSQL in Dockerfile")
elif check == "mysql":
dockerfile_path = os.path.join(project_dir, "Dockerfile")
exists, _ = verify_file(dockerfile_path, "default-libmysqlclient-dev")
if not exists:
all_passed = False
print(" ✗ MySQL client missing in Dockerfile")
else:
print(" ✓ MySQL in Dockerfile")
elif check == "react":
pkg_path = os.path.join(project_dir, "package.json")
exists, _ = verify_file(pkg_path, "react")
if not exists:
all_passed = False
print(" ✗ React not in package.json")
else:
print(" ✓ React in package.json")
elif check == "vue":
pkg_path = os.path.join(project_dir, "package.json")
exists, _ = verify_file(pkg_path, "vue")
if not exists:
all_passed = False
print(" ✗ Vue not in package.json")
else:
print(" ✓ Vue in package.json")
vue_file = os.path.join(project_dir, "frontend/src/App.vue")
exists, _ = verify_file(vue_file)
if not exists:
all_passed = False
print(" ✗ App.vue not found")
else:
print(" ✓ App.vue found")
elif check == "pytest":
req_path = os.path.join(project_dir, "requirements.txt")
exists, _ = verify_file(req_path, "pytest")
if not exists:
all_passed = False
print(" ✗ pytest not in requirements")
else:
print(" ✓ pytest in requirements")
pytest_path = os.path.join(project_dir, "pytest.ini")
exists, _ = verify_file(pytest_path, "DJANGO_SETTINGS_MODULE")
if not exists:
all_passed = False
print(" ✗ pytest.ini incomplete")
else:
print(" ✓ pytest.ini configured")
elif check == "tailwind":
req_path = os.path.join(project_dir, "requirements.txt")
exists, _ = verify_file(req_path, "django-tailwind-cli")
if not exists:
all_passed = False
print(" ✗ Tailwind not in requirements")
else:
print(" ✓ Tailwind in requirements")
elif check == "htmx":
req_path = os.path.join(project_dir, "requirements.txt")
exists, _ = verify_file(req_path, "django-htmx")
if not exists:
all_passed = False
print(" ✗ HTMX not in requirements")
else:
print(" ✓ HTMX in requirements")
if all_passed:
results.append((name, True, "All checks passed"))
print(" ✓ ALL CHECKS PASSED")
else:
results.append((name, False, "; ".join(errors)))
print(" ✗ SOME CHECKS FAILED")
# Summary
print("\n" + "=" * 70)
print(" FINAL SUMMARY")
print("=" * 70)
passed = sum(1 for _, s, _ in results if s)
total = len(results)
print(f"\nTotal Tests: {total}")
print(f"Passed: {passed}")
print(f"Failed: {total - passed}")
print()
for name, success, _msg in results:
status = "✓ PASS" if success else "✗ FAIL"
print(f" {status}: {name}")
print("\n" + "=" * 70)
if passed == total:
print(" 🎉 ALL TESTS PASSED! 🎉")
else:
print(" ⚠️ SOME TESTS FAILED!")
print("=" * 70)
if __name__ == "__main__":
run_all_tests()
+405
View File
@@ -0,0 +1,405 @@
#!/usr/bin/env python3
"""
Test script to show hello world examples for all project structures and cases.
Run from the djinit project root: PYTHONPATH=src python3 examples/test_all_structures.py
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) # noqa: E402
from djinit.templater import template_engine # noqa: E402
def print_section(title):
print("\n" + "=" * 70)
print(f" {title}")
print("=" * 70)
def print_subsection(title):
print(f"\n{'─' * 70}")
print(f" {title}")
print(f"{'─' * 70}")
# Define all test cases
CASES = [
(
"Basic (no optional features)",
{
"use_docker": False,
"use_vite": False,
"use_tailwind": False,
"use_htmx": False,
"database_type": "postgresql",
"use_database_url": True,
},
),
(
"Docker only",
{
"use_docker": True,
"use_vite": False,
"use_tailwind": False,
"use_htmx": False,
"database_type": "postgresql",
"use_database_url": True,
},
),
(
"Vite/React only",
{
"use_docker": False,
"use_vite": True,
"use_tailwind": False,
"use_htmx": False,
"database_type": "postgresql",
"use_database_url": True,
},
),
(
"Docker + Vite",
{
"use_docker": True,
"use_vite": True,
"use_tailwind": False,
"use_htmx": False,
"database_type": "postgresql",
"use_database_url": True,
},
),
(
"Tailwind only",
{
"use_docker": False,
"use_vite": False,
"use_tailwind": True,
"use_htmx": False,
"database_type": "postgresql",
"use_database_url": True,
},
),
(
"HTMX only",
{
"use_docker": False,
"use_vite": False,
"use_tailwind": False,
"use_htmx": True,
"database_type": "postgresql",
"use_database_url": True,
},
),
(
"Tailwind + HTMX",
{
"use_docker": False,
"use_vite": False,
"use_tailwind": True,
"use_htmx": True,
"database_type": "postgresql",
"use_database_url": True,
},
),
(
"All: Docker + Vite + Tailwind + HTMX + PostgreSQL",
{
"use_docker": True,
"use_vite": True,
"use_tailwind": True,
"use_htmx": True,
"database_type": "postgresql",
"use_database_url": True,
},
),
(
"All: Docker + Vite + Tailwind + HTMX + MySQL",
{
"use_docker": True,
"use_vite": True,
"use_tailwind": True,
"use_htmx": True,
"database_type": "mysql",
"use_database_url": True,
},
),
]
STRUCTURES = [
("standard", "config", "Standard Structure (default Django layout)"),
("predefined", "config", "Predefined Structure (apps/users, apps/core, api/)"),
("unified", "core", "Unified Structure (core/, apps/core, apps/api)"),
("single", "myproject", "Single Folder Layout (everything in one folder)"),
]
def show_file_tree(struct_type, case_name, metadata):
"""Show expected file tree for a structure + case combination."""
module_name = {"standard": "config", "predefined": "config", "unified": "core", "single": "myproject"}[struct_type]
features = []
if metadata.get("use_docker"):
features.append("Docker")
if metadata.get("use_vite"):
features.append("Vite")
if metadata.get("use_tailwind"):
features.append("Tailwind")
if metadata.get("use_htmx"):
features.append("HTMX")
features.append(metadata.get("database_type", "postgresql").upper())
print(f" Features: {', '.join(features)}")
print(" Project: myproject/")
print(f" Module: {module_name}/")
indent = " "
# Always present
print(f"{indent}├── .env.sample")
print(f"{indent}├── .gitignore")
print(f"{indent}├── requirements.txt")
print(f"{indent}├── pyproject.toml")
print(f"{indent}├── Procfile")
print(f"{indent}├── justfile")
print(f"{indent}├── runtime.txt")
if metadata.get("use_docker"):
print(f"{indent}├── Dockerfile")
print(f"{indent}├── docker-compose.yml")
print(f"{indent}├── .dockerignore")
if struct_type == "standard":
print(f"{indent}├── config/")
print(f"{indent}│ ├── __init__.py")
print(f"{indent}│ ├── settings/")
print(f"{indent}│ │ ├── __init__.py")
print(f"{indent}│ │ ├── base.py")
print(f"{indent}│ │ ├── development.py")
print(f"{indent}│ │ └── production.py")
print(f"{indent}│ ├── urls.py")
print(f"{indent}│ ├── wsgi.py")
print(f"{indent}│ └── asgi.py")
print(f"{indent}├── users/")
print(f"{indent}│ ├── __init__.py")
print(f"{indent}│ ├── models.py")
print(f"{indent}│ ├── views.py")
print(f"{indent}│ ├── urls.py")
print(f"{indent}│ └── apps.py")
print(f"{indent}└── manage.py")
elif struct_type == "predefined":
print(f"{indent}├── config/")
print(f"{indent}│ ├── __init__.py")
print(f"{indent}│ ├── settings/")
print(f"{indent}│ │ ├── __init__.py")
print(f"{indent}│ │ ├── base.py")
print(f"{indent}│ │ ├── development.py")
print(f"{indent}│ │ └── production.py")
print(f"{indent}│ ├── urls.py")
print(f"{indent}│ ├── wsgi.py")
print(f"{indent}│ └── asgi.py")
print(f"{indent}├── apps/")
print(f"{indent}│ ├── __init__.py")
print(f"{indent}│ ├── core/")
print(f"{indent}│ │ ├── __init__.py")
print(f"{indent}│ │ ├── utils/")
print(f"{indent}│ │ ├── mixins/")
print(f"{indent}│ │ └── middleware/")
print(f"{indent}│ └── users/")
print(f"{indent}│ ├── __init__.py")
print(f"{indent}│ └── apps.py")
print(f"{indent}├── api/")
print(f"{indent}│ ├── __init__.py")
print(f"{indent}│ ├── urls.py")
print(f"{indent}│ └── v1/")
print(f"{indent}│ ├── __init__.py")
print(f"{indent}│ └── urls.py")
print(f"{indent}└── manage.py")
elif struct_type == "unified":
print(f"{indent}├── core/")
print(f"{indent}│ ├── __init__.py")
print(f"{indent}│ ├── settings/")
print(f"{indent}│ │ ├── __init__.py")
print(f"{indent}│ │ ├── base.py")
print(f"{indent}│ │ ├── development.py")
print(f"{indent}│ │ └── production.py")
print(f"{indent}│ ├── urls.py")
print(f"{indent}│ ├── wsgi.py")
print(f"{indent}│ └── asgi.py")
print(f"{indent}├── apps/")
print(f"{indent}│ ├── __init__.py")
print(f"{indent}│ ├── apps.py")
print(f"{indent}│ ├── api/")
print(f"{indent}│ │ ├── __init__.py")
print(f"{indent}│ │ ├── urls.py")
print(f"{indent}│ │ └── v1/")
print(f"{indent}│ │ ├── __init__.py")
print(f"{indent}│ │ └── urls.py")
print(f"{indent}│ ├── models/")
print(f"{indent}│ ├── views/")
print(f"{indent}│ ├── serializers/")
print(f"{indent}│ ├── tests/")
print(f"{indent}│ └── urls/")
print(f"{indent}└── manage.py")
else: # single
print(f"{indent}├── myproject/")
print(f"{indent}│ ├── __init__.py")
print(f"{indent}│ ├── settings/")
print(f"{indent}│ │ ├── __init__.py")
print(f"{indent}│ │ ├── base.py")
print(f"{indent}│ │ ├── development.py")
print(f"{indent}│ │ └── production.py")
print(f"{indent}│ ├── urls.py")
print(f"{indent}│ ├── wsgi.py")
print(f"{indent}│ ├── asgi.py")
print(f"{indent}│ ├── api/")
print(f"{indent}│ ├── models/")
print(f"{indent}│ ├── admin/")
print(f"{indent}│ └── tests/")
print(f"{indent}└── manage.py")
if metadata.get("use_vite"):
print(f"{indent}├── frontend/")
print(f"{indent}│ ├── index.html")
print(f"{indent}│ ├── vite.config.js")
print(f"{indent}│ ├── package.json")
print(f"{indent}│ └── src/")
print(f"{indent}│ ├── main.jsx")
print(f"{indent}│ ├── App.jsx")
print(f"{indent}│ └── index.css")
print(f"{indent}└── static/")
if metadata.get("use_tailwind"):
print(f"{indent}├── static/")
print(f"{indent}│ └── css/")
print(f"{indent}└── templates/")
def show_template_examples():
"""Show actual template content for key files."""
print_section("TEMPLATE CONTENT EXAMPLES")
# 1. Requirements.txt examples
print_subsection("1. requirements.txt - Basic (No optional features)")
ctx = {
"use_vite": False,
"use_tailwind": False,
"use_htmx": False,
"database_type": "postgresql",
"use_database_url": True,
}
print(template_engine.render_template("project/requirements-tpl", ctx))
print_subsection("2. requirements.txt - All Features (PostgreSQL)")
ctx = {
"use_vite": True,
"use_tailwind": True,
"use_htmx": True,
"database_type": "postgresql",
"use_database_url": True,
}
print(template_engine.render_template("project/requirements-tpl", ctx))
print_subsection("3. requirements.txt - All Features (MySQL)")
ctx = {"use_vite": True, "use_tailwind": True, "use_htmx": True, "database_type": "mysql", "use_database_url": True}
print(template_engine.render_template("project/requirements-tpl", ctx))
# 4. Settings examples
print_subsection("4. settings/base.py - With Vite enabled")
ctx = {
"project_name": "config",
"app_names": ["users"],
"use_vite": True,
"use_tailwind": False,
"use_htmx": False,
"use_database_url": True,
"database_type": "postgresql",
}
result = template_engine.render_template("config/settings/base.py-tpl", ctx)
for line in result.split("\n"):
if "django_vite" in line or "DJANGO_VITE" in line:
print(line)
print_subsection("5. settings/base.py - With Tailwind enabled")
ctx = {
"project_name": "config",
"app_names": ["users"],
"use_vite": False,
"use_tailwind": True,
"use_htmx": False,
"use_database_url": True,
"database_type": "postgresql",
}
result = template_engine.render_template("config/settings/base.py-tpl", ctx)
for line in result.split("\n"):
if "tailwind" in line.lower() or "daisyui" in line.lower():
print(line)
# 6. Dockerfile example
print_subsection("6. Dockerfile (PostgreSQL)")
ctx = {"project_name": "myproject", "module_name": "config", "database_type": "postgresql"}
print(template_engine.render_template("project/Dockerfile-tpl", ctx))
print_subsection("7. Dockerfile (MySQL)")
ctx = {"project_name": "myproject", "module_name": "config", "database_type": "mysql"}
print(template_engine.render_template("project/Dockerfile-tpl", ctx))
# 8. docker-compose.yml examples
print_subsection("8. docker-compose.yml (PostgreSQL)")
ctx = {"project_name": "myproject", "database_type": "postgresql", "use_database_url": True}
print(template_engine.render_template("project/docker-compose.yml-tpl", ctx))
# 9. vite.config.js
print_subsection("9. vite.config.js")
ctx = {"project_name": "myproject", "module_name": "config"}
print(template_engine.render_template("project/vite.config.js-tpl", ctx))
# 10. package.json
print_subsection("10. package.json")
print(template_engine.render_template("project/package.json-tpl", ctx))
# 11. Frontend examples
print_subsection("11. frontend/index.html")
print(template_engine.render_template("project/frontend/index.html-tpl", ctx))
print_subsection("12. frontend/src/App.jsx")
print(template_engine.render_template("project/frontend/src/App.jsx-tpl", ctx))
def main():
print_section("DJINIT - ALL STRUCTURES & CASES")
# Show all structure + case combinations
for struct_type, _module_name, struct_desc in STRUCTURES:
print_section(struct_desc)
for case_name, metadata in CASES:
print_subsection(case_name)
show_file_tree(struct_type, case_name, metadata)
# Show template content examples
show_template_examples()
print("\n" + "=" * 70)
print(" ALL STRUCTURES & CASES TESTED SUCCESSFULLY!")
print("=" * 70)
print("\nSummary:")
print(f" • {len(STRUCTURES)} project structures")
print(f" • {len(CASES)} feature combinations")
print(f" • Total: {len(STRUCTURES) * len(CASES)} unique configurations")
print("\nEach combination generates:")
print(" - Django project files based on structure type")
print(" - Optional: Docker files (Dockerfile, docker-compose.yml)")
print(" - Optional: Vite/React frontend (vite.config.js, package.json, etc.)")
print(" - Optional: Tailwind CSS configuration")
print(" - Optional: HTMX integration")
if __name__ == "__main__":
main()
+212
View File
@@ -0,0 +1,212 @@
#!/usr/bin/env python3
"""
Comprehensive test for all djinit features including pytest.
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) # noqa: E402
from djinit.creators.setup import SetupCreator # noqa: E402
def create_project(name, metadata):
"""Create a test project."""
test_dir = "/tmp/djinit_pytest_test"
os.makedirs(test_dir, exist_ok=True)
original_cwd = os.getcwd()
try:
os.chdir(test_dir)
full_metadata = {
"package_name": "backend",
"use_github_actions": False,
"use_gitlab_ci": False,
"nested_apps": True,
"nested_dir": "apps",
"use_database_url": True,
"database_type": "postgresql",
"use_tailwind": False,
"use_htmx": False,
"use_docker": False,
"use_vite": False,
"use_pytest": False,
"predefined_structure": False,
"unified_structure": False,
"single_structure": False,
"project_module_name": "config",
**metadata,
}
creator = SetupCreator(
project_dir=name, project_name=name, primary_app="users", app_names=["users"], metadata=full_metadata
)
success = creator.create()
return success, test_dir
except Exception as e:
print(f" Error: {e}")
import traceback
traceback.print_exc()
return False, test_dir
finally:
os.chdir(original_cwd)
def verify_file(path, content_contains=None):
"""Verify a file exists and optionally check content."""
if not os.path.exists(path):
return False, f"File not found: {path}"
if content_contains:
with open(path) as f:
content = f.read()
if content_contains not in content:
return False, f"Content '{content_contains}' not found in {path}"
return True, "OK"
def run_tests():
print("=" * 70)
print(" DJINIT COMPREHENSIVE TESTS (WITH PYTEST)")
print("=" * 70)
test_cases = [
("basic", {}, ["requirements.txt"]),
("docker", {"use_docker": True}, ["Dockerfile", "docker-compose.yml"]),
("vite", {"use_vite": True}, ["vite.config.js", "package.json"]),
("pytest", {"use_pytest": True}, ["pytest.ini", "conftest.py"]),
("tailwind", {"use_tailwind": True}, ["requirements.txt"]),
("htmx", {"use_htmx": True}, ["requirements.txt"]),
(
"all_postgres",
{"use_docker": True, "use_vite": True, "use_tailwind": True, "use_htmx": True, "use_pytest": True},
["Dockerfile", "vite.config.js", "pytest.ini"],
),
(
"all_mysql",
{"use_docker": True, "use_vite": True, "use_tailwind": True, "use_pytest": True, "database_type": "mysql"},
["Dockerfile", "vite.config.js", "pytest.ini"],
),
]
results = []
for name, metadata, expected_files in test_cases:
print(f"\n{'─' * 70}")
print(f" Testing: {name}")
print(f" Metadata: {metadata}")
import shutil
test_dir = "/tmp/djinit_pytest_test"
if os.path.exists(test_dir):
shutil.rmtree(test_dir)
success, _ = create_project(name, metadata)
if not success:
results.append((name, False, "Project creation failed"))
print(" ✗ Failed to create project")
continue
project_dir = os.path.join(test_dir, name)
all_passed = True
errors = []
for file in expected_files:
file_path = os.path.join(project_dir, file)
exists, msg = verify_file(file_path)
if not exists:
all_passed = False
errors.append(msg)
print(f" ✗ {msg}")
else:
print(f" ✓ {file}")
# Special checks for pytest
if metadata.get("use_pytest"):
req_path = os.path.join(project_dir, "requirements.txt")
exists, msg = verify_file(req_path, "pytest")
if not exists:
all_passed = False
print(" ✗ pytest not in requirements")
else:
print(" ✓ pytest in requirements")
pytest_ini_path = os.path.join(project_dir, "pytest.ini")
exists, msg = verify_file(pytest_ini_path, "DJANGO_SETTINGS_MODULE")
if not exists:
all_passed = False
print(" ✗ DJANGO_SETTINGS_MODULE not in pytest.ini")
else:
print(" ✓ pytest.ini has DJANGO_SETTINGS_MODULE")
conftest_path = os.path.join(project_dir, "conftest.py")
exists, msg = verify_file(conftest_path, "pytest.fixture")
if not exists:
all_passed = False
print(" ✗ pytest fixtures not in conftest.py")
else:
print(" ✓ conftest.py has fixtures")
# Special checks for vite
if metadata.get("use_vite"):
settings_path = os.path.join(project_dir, "config/settings/base.py")
exists, msg = verify_file(settings_path, "django_vite")
if not exists:
all_passed = False
print(" ✗ django_vite not in settings")
else:
print(" ✓ django_vite in settings")
# Special checks for docker
if metadata.get("use_docker"):
dockerfile_path = os.path.join(project_dir, "Dockerfile")
db_type = metadata.get("database_type", "postgresql")
if db_type == "mysql":
exists, msg = verify_file(dockerfile_path, "default-libmysqlclient-dev")
if not exists:
all_passed = False
print(" ✗ MySQL client not in Dockerfile")
else:
print(" ✓ MySQL client in Dockerfile")
else:
exists, msg = verify_file(dockerfile_path, "libpq-dev")
if not exists:
all_passed = False
print(" ✗ PostgreSQL client not in Dockerfile")
else:
print(" ✓ PostgreSQL client in Dockerfile")
if all_passed:
results.append((name, True, "All checks passed"))
print(" ✓ ALL CHECKS PASSED")
else:
results.append((name, False, "; ".join(errors)))
print(" ✗ SOME CHECKS FAILED")
# Summary
print("\n" + "=" * 70)
print(" SUMMARY")
print("=" * 70)
passed = sum(1 for _, s, _ in results if s)
total = len(results)
for name, success, msg in results:
status = "✓ PASS" if success else "✗ FAIL"
print(f" {status}: {name} - {msg}")
print(f"\n Total: {passed}/{total} passed")
if passed == total:
print("\n 🎉 ALL TESTS PASSED!")
else:
print("\n ⚠️ SOME TESTS FAILED!")
if __name__ == "__main__":
run_tests()
+174
View File
@@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""
Comprehensive test for all djinit features including Vue.
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) # noqa: E402
from djinit.creators.setup import SetupCreator # noqa: E402
def create_project(name, metadata):
test_dir = "/tmp/djinit_vue_test"
os.makedirs(test_dir, exist_ok=True)
original_cwd = os.getcwd()
try:
os.chdir(test_dir)
full_metadata = {
"package_name": "backend",
"use_github_actions": False,
"use_gitlab_ci": False,
"nested_apps": True,
"nested_dir": "apps",
"use_database_url": True,
"database_type": "postgresql",
"use_tailwind": False,
"use_htmx": False,
"use_docker": False,
"use_vite": False,
"use_vue": False,
"use_pytest": False,
"predefined_structure": False,
"unified_structure": False,
"single_structure": False,
"project_module_name": "config",
**metadata,
}
creator = SetupCreator(
project_dir=name, project_name=name, primary_app="users", app_names=["users"], metadata=full_metadata
)
success = creator.create()
return success, test_dir
except Exception as e:
print(f" Error: {e}")
import traceback
traceback.print_exc()
return False, test_dir
finally:
os.chdir(original_cwd)
def verify_file(path, content_contains=None):
if not os.path.exists(path):
return False, f"File not found: {path}"
if content_contains:
with open(path) as f:
content = f.read()
if content_contains not in content:
return False, f"Content '{content_contains}' not found in {path}"
return True, "OK"
def run_tests():
print("=" * 70)
print(" DJINIT COMPREHENSIVE TESTS (WITH VUE)")
print("=" * 70)
test_cases = [
("basic", {}, ["requirements.txt"]),
("docker", {"use_docker": True}, ["Dockerfile"]),
("react", {"use_vite": True}, ["vite.config.js", "package.json"]),
("vue", {"use_vue": True}, ["vite.config.js", "package.json"]),
("pytest", {"use_pytest": True}, ["pytest.ini"]),
("all_react", {"use_docker": True, "use_vite": True, "use_pytest": True}, ["Dockerfile", "vite.config.js"]),
("all_vue", {"use_docker": True, "use_vue": True, "use_pytest": True}, ["Dockerfile", "vite.config.js"]),
]
results = []
for name, metadata, expected_files in test_cases:
print(f"\n{'─' * 70}")
print(f" Testing: {name}")
print(f" Metadata: {metadata}")
import shutil
test_dir = "/tmp/djinit_vue_test"
if os.path.exists(test_dir):
shutil.rmtree(test_dir)
success, _ = create_project(name, metadata)
if not success:
results.append((name, False, "Project creation failed"))
print(" ✗ Failed to create project")
continue
project_dir = os.path.join(test_dir, name)
all_passed = True
errors = []
for file in expected_files:
file_path = os.path.join(project_dir, file)
exists, msg = verify_file(file_path)
if not exists:
all_passed = False
errors.append(msg)
print(f" ✗ {msg}")
else:
print(f" ✓ {file}")
# Special checks for React
if metadata.get("use_vite"):
pkg_path = os.path.join(project_dir, "package.json")
exists, msg = verify_file(pkg_path, "react")
if not exists:
all_passed = False
print(" ✗ React not in package.json")
else:
print(" ✓ React in package.json")
# Special checks for Vue
if metadata.get("use_vue"):
pkg_path = os.path.join(project_dir, "package.json")
exists, msg = verify_file(pkg_path, "vue")
if not exists:
all_passed = False
print(" ✗ Vue not in package.json")
else:
print(" ✓ Vue in package.json")
vue_app = os.path.join(project_dir, "frontend/src/App.vue")
exists, msg = verify_file(vue_app)
if not exists:
all_passed = False
print(" ✗ App.vue not found")
else:
print(" ✓ App.vue found")
if all_passed:
results.append((name, True, "All checks passed"))
print(" ✓ ALL CHECKS PASSED")
else:
results.append((name, False, "; ".join(errors)))
print(" ✗ SOME CHECKS FAILED")
print("\n" + "=" * 70)
print(" SUMMARY")
print("=" * 70)
passed = sum(1 for _, s, _ in results if s)
total = len(results)
for name, success, msg in results:
status = "✓ PASS" if success else "✗ FAIL"
print(f" {status}: {name} - {msg}")
print(f"\n Total: {passed}/{total} passed")
if passed == total:
print("\n 🎉 ALL TESTS PASSED!")
else:
print("\n ⚠️ SOME TESTS FAILED!")
if __name__ == "__main__":
run_tests()
+21 -46
View File
@@ -14,24 +14,29 @@ class InFileLogicParser:
def _get_value(self, key: str) -> str:
"""
Supports nested/dotted access like [[ user.name ]] or [[ settings["DEBUG"] ]].
For simplicity, it uses eval in the provided context for complex expressions,
but falls back to safe dictionary lookup for simple keys.
"""
key = key.strip()
try:
# Try evaluating the expression in the context
return str(eval(key, {"__builtins__": {}}, self.context))
except (NameError, SyntaxError, KeyError, TypeError, AttributeError):
# Fallback for common patterns or just return the key as is if it fails
return f"[[ {key} ]]"
def _eval_expr(self, expr: str) -> bool:
"""Safely evaluate a boolean expression in the context. Defaults missing variables to False."""
safe_context = dict(self.context)
for key in re.findall(r"\b([a-zA-Z_][a-zA-Z0-9_]*)\b", expr):
if key not in safe_context:
safe_context[key] = False
try:
return bool(eval(expr, {"__builtins__": {}}, safe_context))
except Exception:
return False
def render(self, template_text: str, context: Dict[str, Any] = None) -> str:
if context is not None:
self.context = context
final_lines = []
# Stack stores boolean results of nested IF blocks
# Each element is (current_block_result, has_any_true_branch_executed)
stack: List[List[bool]] = []
lines = template_text.splitlines()
@@ -40,18 +45,13 @@ class InFileLogicParser:
line = lines[i]
stripped = line.strip()
# Handle @IF
if stripped.startswith("# @IF "):
expr = stripped[6:].strip()
try:
result = bool(eval(expr, {"__builtins__": {}}, self.context))
except Exception:
result = False
result = self._eval_expr(expr)
stack.append([result, result])
i += 1
continue
# Handle @ELSEIF
elif stripped.startswith("# @ELSEIF "):
if not stack:
final_lines.append(line)
@@ -64,17 +64,13 @@ class InFileLogicParser:
if current_stack[1]:
current_stack[0] = False
else:
try:
result = bool(eval(expr, {"__builtins__": {}}, self.context))
except Exception:
result = False
result = self._eval_expr(expr)
current_stack[0] = result
if result:
current_stack[1] = True
i += 1
continue
# Handle @ELSE
elif stripped.startswith("# @ELSE"):
if not stack:
final_lines.append(line)
@@ -90,7 +86,6 @@ class InFileLogicParser:
i += 1
continue
# Handle @ENDIF
elif stripped.startswith("# @ENDIF"):
if stack:
stack.pop()
@@ -99,10 +94,8 @@ class InFileLogicParser:
i += 1
continue
# Handle @LOOP
elif stripped.startswith("# @LOOP "):
if stack and not all(s[0] for s in stack):
# Skip the entire loop block if inside a false conditional
loop_depth = 1
i += 1
while i < len(lines) and loop_depth > 0:
@@ -122,7 +115,6 @@ class InFileLogicParser:
except Exception:
iterable = []
# Capture loop body
loop_body = []
i += 1
loop_depth = 1
@@ -131,15 +123,13 @@ class InFileLogicParser:
loop_depth += 1
elif lines[i].strip().startswith("# @ENDLOOP"):
loop_depth -= 1
if loop_depth > 0:
loop_body.append(lines[i])
i += 1
if i < len(lines): # Skip the @ENDLOOP line itself
i += 1
# Execute loop
if i < len(lines):
i += 1
if hasattr(iterable, "__iter__"):
old_val = self.context.get(var_name)
for val in iterable:
@@ -156,35 +146,25 @@ class InFileLogicParser:
i += 1
continue
# Handle @ENDLOOP (should only be hit if out of sync or error)
elif stripped.startswith("# @ENDLOOP"):
i += 1
continue
# Check if we should skip this line based on conditional stack
if stack and not all(s[0] for s in stack):
i += 1
continue
# Variable substitution
rendered_line = line
matches = re.findall(r"\[\[\s*(.*?)\s*\]\]", rendered_line)
for match in matches:
value = self._get_value(match)
# Replace all variations of the variable syntax
rendered_line = re.sub(r"\[\[\s*" + re.escape(match) + r"\s*\]\]", value, rendered_line)
# In-line @IF support
# Case 1: content # @IF cond
# Case 2: # @IF cond content
if "# @IF " in rendered_line:
parts = rendered_line.split("# @IF ", 1)
pre_content = parts[0].rstrip()
rest = parts[1].strip()
# Split rest into expression and post-content (if any)
# This is tricky because the expression might have spaces.
# If there's an # @ENDIF on the same line, use it.
post_content = ""
expr = rest
if " # @ENDIF" in rest:
@@ -196,16 +176,11 @@ class InFileLogicParser:
expr = expr_parts[0].strip()
post_content = expr_parts[1].lstrip()
try:
if bool(eval(expr, {"__builtins__": {}}, self.context)):
# If Case 1, we want pre_content. If Case 2, we want post_content.
# Usually, if pre_content is empty (or just whitespace), it's Case 2.
if pre_content:
final_lines.append(pre_content + post_content)
else:
final_lines.append(post_content)
except Exception:
pass
if self._eval_expr(expr):
if pre_content:
final_lines.append(pre_content + post_content)
else:
final_lines.append(post_content)
i += 1
continue
+11
View File
@@ -17,6 +17,12 @@ class ProjectMetadata:
database_type: str = "postgresql"
use_tailwind: bool = False
use_htmx: bool = False
use_docker: bool = False
use_vite: bool = False
use_vue: bool = False
use_pytest: bool = False
use_django_q: bool = False
use_celery: bool = False
predefined_structure: bool = False
unified_structure: bool = False
single_structure: bool = False
@@ -33,6 +39,11 @@ class ProjectMetadata:
"database_type": self.database_type,
"use_tailwind": self.use_tailwind,
"use_htmx": self.use_htmx,
"use_docker": self.use_docker,
"use_vite": self.use_vite,
"use_vue": self.use_vue,
"use_pytest": self.use_pytest,
"use_django_q": self.use_django_q,
"predefined_structure": self.predefined_structure,
"unified_structure": self.unified_structure,
"single_structure": self.single_structure,
+162 -1
View File
@@ -88,9 +88,14 @@ class FileCreator(BaseService):
for name in base_settings_context["app_names"]
]
# Add Tailwind and HTMX to context
# Add Tailwind, HTMX, Vite, Vue, and pytest to context
base_settings_context["use_tailwind"] = self.metadata.get("use_tailwind", False)
base_settings_context["use_htmx"] = self.metadata.get("use_htmx", False)
base_settings_context["use_vite"] = self.metadata.get("use_vite", False)
base_settings_context["use_vue"] = self.metadata.get("use_vue", False)
base_settings_context["use_pytest"] = self.metadata.get("use_pytest", False)
base_settings_context["use_django_q"] = self.metadata.get("use_django_q", False)
base_settings_context["use_celery"] = self.metadata.get("use_celery", False)
for filename, context in [
("base.py", base_settings_context),
@@ -140,6 +145,13 @@ class FileCreator(BaseService):
"database_type": self.metadata.get("database_type", "postgresql"),
"use_tailwind": self.metadata.get("use_tailwind", False),
"use_htmx": self.metadata.get("use_htmx", False),
"use_vite": self.metadata.get("use_vite", False),
"use_vue": self.metadata.get("use_vue", False),
"use_pytest": self.metadata.get("use_pytest", False),
"use_django_q": self.metadata.get("use_django_q", False),
"use_celery": self.metadata.get("use_celery", False),
"module_name": self.module_name,
"app_names": self.app_names,
}
self._render_and_create_file(
"requirements.txt",
@@ -421,6 +433,10 @@ class FileCreator(BaseService):
"database_type": self.metadata.get("database_type", "postgresql"),
"use_tailwind": self.metadata.get("use_tailwind", False),
"use_htmx": self.metadata.get("use_htmx", False),
"use_docker": self.metadata.get("use_docker", False),
"use_vite": self.metadata.get("use_vite", False),
"use_vue": self.metadata.get("use_vue", False),
"use_pytest": self.metadata.get("use_pytest", False),
},
"cicd": {
"github": self.metadata.get("use_github_actions", False),
@@ -432,3 +448,148 @@ class FileCreator(BaseService):
CommonUtils.create_file_with_content(
filepath, json.dumps(config, indent=4), "Created .djinit configuration file"
)
def create_docker_files(self) -> None:
"""Create Docker-related files (Dockerfile, docker-compose.yml, .dockerignore)."""
context = {
"project_name": self.project_name,
"module_name": self.module_name,
"database_type": self.metadata.get("database_type", "postgresql"),
"use_database_url": self.metadata.get("use_database_url", True),
}
self._render_and_create_file(
"Dockerfile",
"project/Dockerfile-tpl",
context,
"Created Dockerfile for Django application",
)
self._render_and_create_file(
"docker-compose.yml",
"project/docker-compose.yml-tpl",
context,
"Created docker-compose.yml for local development",
)
self._render_and_create_file(
".dockerignore",
"project/dockerignore-tpl",
{},
"Created .dockerignore file",
)
def create_vite_files(self) -> None:
"""Create Vite/React frontend files."""
context = {
"project_name": self.project_name,
"module_name": self.module_name,
}
# Create frontend directories
frontend_dir = os.path.join(self.project_root, "frontend")
frontend_src_dir = os.path.join(frontend_dir, "src")
static_dir = os.path.join(self.project_root, "static")
os.makedirs(frontend_dir, exist_ok=True)
os.makedirs(frontend_src_dir, exist_ok=True)
os.makedirs(static_dir, exist_ok=True)
# Create __init__.py files
CommonUtils.create_directory_with_init(frontend_dir, "Created frontend/")
CommonUtils.create_directory_with_init(frontend_src_dir, "Created frontend/src/")
CommonUtils.create_directory_with_init(static_dir, "Created static/")
self._render_and_create_file(
"vite.config.js",
"project/vite.config.js-tpl",
context,
"Created vite.config.js",
)
self._render_and_create_file(
"package.json",
"project/package.json-tpl",
context,
"Created package.json for frontend",
)
index_html_path = os.path.join(frontend_dir, "index.html")
CommonUtils.create_file_from_template(
index_html_path, "project/frontend/index.html-tpl", context, "Created frontend/index.html"
)
main_jsx_path = os.path.join(frontend_src_dir, "main.jsx")
CommonUtils.create_file_from_template(
main_jsx_path, "project/frontend/src/main.jsx-tpl", context, "Created frontend/src/main.jsx"
)
app_jsx_path = os.path.join(frontend_src_dir, "App.jsx")
CommonUtils.create_file_from_template(
app_jsx_path, "project/frontend/src/App.jsx-tpl", context, "Created frontend/src/App.jsx"
)
index_css_path = os.path.join(frontend_src_dir, "index.css")
CommonUtils.create_file_from_template(
index_css_path, "project/frontend/src/index.css-tpl", context, "Created frontend/src/index.css"
)
def create_vue_files(self) -> None:
"""Create Vite/Vue frontend files."""
context = {
"project_name": self.project_name,
"module_name": self.module_name,
}
frontend_dir = os.path.join(self.project_root, "frontend")
frontend_src_dir = os.path.join(frontend_dir, "src")
static_dir = os.path.join(self.project_root, "static")
os.makedirs(frontend_dir, exist_ok=True)
os.makedirs(frontend_src_dir, exist_ok=True)
os.makedirs(static_dir, exist_ok=True)
CommonUtils.create_directory_with_init(frontend_dir, "Created frontend/")
CommonUtils.create_directory_with_init(frontend_src_dir, "Created frontend/src/")
CommonUtils.create_directory_with_init(static_dir, "Created static/")
self._render_and_create_file(
"vite.config.js",
"project/vite-vue.config.js-tpl",
context,
"Created vite.config.js for Vue",
)
self._render_and_create_file(
"package.json",
"project/package-vue.json-tpl",
context,
"Created package.json for Vue frontend",
)
index_html_path = os.path.join(frontend_dir, "index.html")
CommonUtils.create_file_from_template(
index_html_path, "project/frontend/index-vue.html-tpl", context, "Created frontend/index.html"
)
main_js_path = os.path.join(frontend_src_dir, "main.js")
CommonUtils.create_file_from_template(
main_js_path, "project/frontend/src/main.js-tpl", context, "Created frontend/src/main.js"
)
app_vue_path = os.path.join(frontend_src_dir, "App.vue")
CommonUtils.create_file_from_template(
app_vue_path, "project/frontend/src/App.vue-tpl", context, "Created frontend/src/App.vue"
)
style_css_path = os.path.join(frontend_src_dir, "style.css")
CommonUtils.create_file_from_template(
style_css_path, "project/frontend/src/style.css-tpl", context, "Created frontend/src/style.css"
)
def create_pytest_files(self) -> None:
"""Create pytest configuration files."""
context = {
"project_name": self.project_name,
"module_name": self.module_name,
"app_names": self.app_names or [],
}
self._render_and_create_file(
"pytest.ini",
"project/pytest.ini-tpl",
context,
"Created pytest.ini configuration",
)
self._render_and_create_file(
"conftest.py",
"project/conftest.py-tpl",
context,
"Created conftest.py with pytest fixtures",
)
+43 -8
View File
@@ -128,16 +128,51 @@ class ProjectCreator(BaseService):
# Core Django project files
required_files = [
join("manage.py"),
join(self.module_name, "__init__.py"),
join(self.module_name, "settings", "__init__.py"),
join(self.module_name, "settings", "base.py"),
join(self.module_name, "settings", "development.py"),
join(self.module_name, "settings", "production.py"),
join(self.module_name, "urls.py"),
join(self.module_name, "wsgi.py"),
join(self.module_name, "asgi.py"),
]
if self.metadata.get("unified_structure"):
# Unified structure: settings in core/
required_files.extend(
[
join("core", "__init__.py"),
join("core", "settings", "__init__.py"),
join("core", "settings", "base.py"),
join("core", "settings", "development.py"),
join("core", "settings", "production.py"),
join("core", "urls.py"),
join("core", "wsgi.py"),
join("core", "asgi.py"),
]
)
elif self.metadata.get("single_structure"):
# Single structure: in project_name folder
required_files.extend(
[
join(self.module_name, "__init__.py"),
join(self.module_name, "settings", "__init__.py"),
join(self.module_name, "settings", "base.py"),
join(self.module_name, "settings", "development.py"),
join(self.module_name, "settings", "production.py"),
join(self.module_name, "urls.py"),
join(self.module_name, "wsgi.py"),
join(self.module_name, "asgi.py"),
]
)
else:
# Standard/Predefined structure
required_files.extend(
[
join(self.module_name, "__init__.py"),
join(self.module_name, "settings", "__init__.py"),
join(self.module_name, "settings", "base.py"),
join(self.module_name, "settings", "development.py"),
join(self.module_name, "settings", "production.py"),
join(self.module_name, "urls.py"),
join(self.module_name, "wsgi.py"),
join(self.module_name, "asgi.py"),
]
)
apps_base_dir = self._get_apps_base_dir()
if self.metadata.get("unified_structure"):
+24
View File
@@ -77,6 +77,10 @@ class SetupCreator:
("Creating Justfile", self.file_creator.create_justfile),
("Creating runtime.txt", self.file_creator.create_runtime_txt),
("Creating CI/CD pipelines", self._create_cicd_pipelines),
("Creating Docker files", self._create_docker_files),
("Creating Vite/React files", self._create_vite_files),
("Creating Vite/Vue files", self._create_vue_files),
("Creating pytest files", self._create_pytest_files),
]
)
@@ -118,3 +122,23 @@ class SetupCreator:
if self.metadata.get("use_gitlab_ci", True):
self.file_creator.create_gitlab_ci()
def _create_docker_files(self) -> None:
if self.metadata.get("use_docker", False):
self.file_creator.create_docker_files()
UIFormatter.print_success("Created Docker files successfully!")
def _create_vite_files(self) -> None:
if self.metadata.get("use_vite", False):
self.file_creator.create_vite_files()
UIFormatter.print_success("Created Vite/React files successfully!")
def _create_vue_files(self) -> None:
if self.metadata.get("use_vue", False):
self.file_creator.create_vue_files()
UIFormatter.print_success("Created Vite/Vue files successfully!")
def _create_pytest_files(self) -> None:
if self.metadata.get("use_pytest", False):
self.file_creator.create_pytest_files()
UIFormatter.print_success("Created pytest files successfully!")
@@ -28,6 +28,9 @@ THIRD_PARTY_APPS = [
"drf_spectacular",
"django_tailwind_cli", # @IF use_tailwind
"django_htmx", # @IF use_htmx
"django_vite", # @IF use_vite | use_vue
"django_q", # @IF use_django_q
"celery", # @IF use_celery
]
USER_DEFINED_APPS = [
@@ -225,3 +228,39 @@ TAILWIND_CLI_CONFIG = {
},
}
# @ENDIF
# @IF use_vite | use_vue
# for django-vite
DJANGO_VITE_ASSET_SRC = "frontend"
DJANGO_VITE_DEV_SERVER_URL = "http://localhost:5173"
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_DIRS = [BASE_DIR / "static"]
# @ENDIF
# @IF use_django_q
# Django-Q2 configuration
Q_CLUSTER = {
"name": "djinit_project",
"workers": 4,
"max_threads": 30,
"timeout": 90,
"retry": 120,
"queue_limit": 50,
"bulk": 10,
"orm": "default",
"cache": "default",
}
# @ENDIF
# @IF use_celery
# Celery configuration
CELERY_BROKER_URL = env("CELERY_BROKER_URL", default="redis://localhost:6379/0")
CELERY_RESULT_BACKEND = env("CELERY_RESULT_BACKEND", default="redis://localhost:6379/0")
CELERY_ACCEPT_CONTENT = ["json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
CELERY_TIMEZONE = TIME_ZONE
CELERY_TASK_TRACK_STARTED = True
CELERY_TASK_TIME_LIMIT = 30 * 60
# @ENDIF
@@ -0,0 +1,30 @@
#--------------------------------------------------#
# The following was generated by djinit: #
#--------------------------------------------------#
FROM python:3.13-slim
WORKDIR /app
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libpq-dev \
# @IF database_type == 'mysql'
default-libmysqlclient-dev \
build-essential \
# @ENDIF
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["gunicorn", "[[ module_name ]].wsgi:application", "--bind", "0.0.0.0:8000"]
@@ -0,0 +1,46 @@
#--------------------------------------------------#
# The following was generated by djinit: #
#--------------------------------------------------#
import os
import sys
import django
from pathlib import Path
# Add project to path
sys.path.insert(0, str(Path(__file__).parent.parent))
# Configure Django settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '[[ module_name ]].settings.development')
django.setup()
import pytest
@pytest.fixture(scope="session")
def django_db_setup():
"""Setup test database."""
from django.conf import settings
settings.DATABASES['default'] = {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
@pytest.fixture
def api_client():
"""Return a test client."""
from rest_framework.test import APIClient
return APIClient()
@pytest.fixture
def user(db):
"""Create a test user."""
from django.contrib.auth import get_user_model
User = get_user_model()
return User.objects.create_user(
username='testuser',
email='test@example.com',
password='testpass123'
)
@@ -0,0 +1,55 @@
#--------------------------------------------------#
# The following was generated by djinit: #
#--------------------------------------------------#
services:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/app
ports:
- "8000:8000"
environment:
- DEBUG=1
# @IF use_database_url
# @IF database_type == 'postgresql'
- DATABASE_URL=postgresql://postgres:postgres@db:5432/[[ project_name ]]
# @ELSE
- DATABASE_URL=mysql://root:root@db:3306/[[ project_name ]]
# @ENDIF
# @ENDIF
depends_on:
- db
# @IF database_type == 'postgresql'
db:
image: postgres:16-alpine
environment:
- POSTGRES_DB=[[ project_name ]]
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
# @ELSE
db:
image: mysql:8
environment:
- MYSQL_DATABASE=[[ project_name ]]
- MYSQL_ROOT_PASSWORD=root
- MYSQL_PASSWORD=root
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
# @ENDIF
# @IF database_type == 'postgresql'
volumes:
postgres_data:
# @ELSE
volumes:
mysql_data:
# @ENDIF
@@ -0,0 +1,55 @@
#--------------------------------------------------#
# The following was generated by djinit: #
#--------------------------------------------------#
# Git
.git
.gitignore
# Python
__pycache__
*.py[cod]
*$py.class
*.egg-info/
dist/
build/
.eggs/
.venv/
venv/
env/
# IDE
.vscode/
.idea/
*.swp
# Environment
.env
.env.*
!.env.example
# Database
*.sqlite3
*.db
# Logs
*.log
logs/
# Docker
docker-compose*.yml
Dockerfile
.dockerignore
# OS
.DS_Store
Thumbs.db
# Testing
.coverage
htmlcov/
.pytest_cache/
# Documentation
*.md
!README.md
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>[[ project_name ]]</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>[[ project_name ]]</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
@@ -0,0 +1,11 @@
function App() {
return (
<div style={{ padding: '2rem', fontFamily: 'system-ui, sans-serif' }}>
<h1>Welcome to [[ project_name ]]</h1>
<p>This is your React frontend powered by Vite.</p>
<p>Edit <code>frontend/src/App.jsx</code> to get started!</p>
</div>
)
}
export default App
@@ -0,0 +1,17 @@
<script setup>
</script>
<template>
<div class="container">
<h1>Welcome to [[ project_name ]]</h1>
<p>This is your Vue.js frontend powered by Vite.</p>
<p>Edit <code>frontend/src/App.vue</code> to get started!</p>
</div>
</template>
<style scoped>
.container {
padding: 2rem;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
</style>
@@ -0,0 +1,14 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.5;
}
#root {
min-height: 100vh;
}
@@ -0,0 +1,5 @@
import { createApp } from 'vue'
import App from './App.vue'
import './style.css'
createApp(App).mount('#app')
@@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
@@ -0,0 +1,14 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.5;
}
#app {
min-height: 100vh;
}
@@ -91,3 +91,12 @@ secrets.json
# Cloud
.cloud
# Docker
.dockerignore
docker-compose.override.yml
*.tar
# Docker volumes
postgres_data/
mysql_data/
@@ -0,0 +1,22 @@
#--------------------------------------------------#
# The following was generated by djinit: #
#--------------------------------------------------#
{
"name": "[[ project_name ]]-frontend",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.5.13"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.3",
"vite": "^6.3.5"
}
}
@@ -0,0 +1,23 @@
#--------------------------------------------------#
# The following was generated by djinit: #
#--------------------------------------------------#
{
"name": "[[ project_name ]]-frontend",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.3.4",
"vite": "^6.3.5"
}
}
@@ -0,0 +1,14 @@
#--------------------------------------------------#
# The following was generated by djinit: #
#--------------------------------------------------#
[pytest]
DJANGO_SETTINGS_MODULE = [[ module_name ]].settings.development
python_files = tests.py test_*.py *_tests.py
python_classes = Test* *Tests *Test
python_functions = test_*
addopts = --strict-markers --tb=short
testpaths = [[ app_names ]]
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
integration: marks tests as integration tests
+81 -11
View File
@@ -1,13 +1,35 @@
# [[ project_name ]]
A Django project folder structure is generated with [djinitx/djinit/dj](https://github.com/S4NKALP/djinit).
A Django project generated with [djinitx/djinit/dj](https://github.com/S4NKALP/djinit).
## Features
- Modern project structure with environment-specific settings
- Pre-configured REST API with JWT authentication
- Essential dependencies and utilities
- Production-ready configuration
- Split settings for development and production
- API documentation (Swagger UI at `/docs/`)
- CORS configured for local development
- WhiteNoise for static files
- Modern admin interface (django-jazzmin)
# @IF use_docker
- Docker support (Dockerfile, docker-compose.yml)
# @ENDIF
# @IF use_vite | use_vue
- Frontend integration (Vite + React or Vue.js via django-vite)
# @ENDIF
# @IF use_pytest
- Testing framework (pytest, pytest-django, pytest-cov)
# @ENDIF
# @IF use_django_q
- Async task queue (Django-Q2)
# @ENDIF
# @IF use_celery
- Async task queue (Celery with Redis broker)
# @ENDIF
- Production-ready configuration (Procfile, runtime.txt)
- Development tools (Justfile with common commands)
- Environment template (.env.sample)
- Git ready (.gitignore included)
## Setup
@@ -158,6 +180,33 @@ Run `just` to see all available commands, including:
├── Procfile
├── runtime.txt
└── README.md
# @ELSEIF single_structure
[[ project_name ]]/
├── [[ module_name ]]/
│ ├── settings/
│ │ ├── base.py
│ │ ├── development.py
│ │ └── production.py
│ ├── urls.py
│ ├── wsgi.py
│ └── asgi.py
├── admin/
├── api/ # API endpoints organized by model
│ └── user/ # Example: if you have a User model
│ ├── views.py
│ ├── serializers.py
│ └── urls.py
├── models/
├── tests/
├── manage.py
├── requirements.txt
├── pyproject.toml
├── uv.lock
├── .env.sample
├── justfile
├── Procfile
├── runtime.txt
└── README.md
# @ELSE
[[ project_name ]]/
├── [[ module_name ]]/
@@ -182,15 +231,10 @@ Run `just` to see all available commands, including:
├── pyproject.toml
├── uv.lock
├── .env.sample
├── justfile
├── Procfile
├── runtime.txt
└── README.md
**Note:** For the single folder layout, organize your API endpoints by model name under the `api/` directory.
For example, if you have a `User` model in `models/user.py`, create:
- `api/user/views.py` - API views for User
- `api/user/serializers.py` - Serializers for User
- `api/user/urls.py` - URL patterns for User endpoints
See the README files in `api/` and `models/` directories for more details.
# @ENDIF
```
@@ -199,3 +243,29 @@ See the README files in `api/` and `models/` directories for more details.
When running in development mode, API documentation is available at:
- Swagger UI: http://localhost:8000/docs/
- Schema: http://localhost:8000/schema/
# @IF use_django_q
## Django-Q2
Django-Q2 is a task queue for Django. To start the worker:
```bash
python manage.py qcluster
```
# @ENDIF
# @IF use_celery
## Celery
Celery is a distributed task queue. To start the worker:
```bash
celery -A [[ module_name ]] worker -l info
```
To start the beat scheduler:
```bash
celery -A [[ module_name ]] beat -l info
```
# @ENDIF
@@ -12,6 +12,13 @@ django-cors-headers
whitenoise
django-tailwind-cli # @IF use_tailwind
django-htmx # @IF use_htmx
django-vite # @IF use_vite
django-vite # @IF use_vue
pytest # @IF use_pytest
pytest-django # @IF use_pytest
pytest-cov # @IF use_pytest
django-q2 # @IF use_django_q
celery # @IF use_celery
# @IF database_type == 'mysql'
mysqlclient
# @ELSE
@@ -0,0 +1,25 @@
#--------------------------------------------------#
# The following was generated by djinit: #
#--------------------------------------------------#
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
root: 'frontend',
build: {
outDir: '../static/dist',
emptyOutDir: true,
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'frontend/src'),
},
},
server: {
port: 5173,
strictPort: true,
},
})
@@ -0,0 +1,25 @@
#--------------------------------------------------#
# The following was generated by djinit: #
#--------------------------------------------------#
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
root: 'frontend',
build: {
outDir: '../static/dist',
emptyOutDir: true,
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'frontend/src'),
},
},
server: {
port: 5173,
strictPort: true,
},
})
+77
View File
@@ -25,6 +25,12 @@ class StructureOptions(TypedDict):
use_database_url: bool
use_tailwind: bool
use_htmx: bool
use_docker: bool
use_vite: bool
use_vue: bool
use_pytest: bool
use_django_q: bool
use_celery: bool
use_github: bool
use_gitlab: bool
@@ -179,6 +185,24 @@ class InputCollector:
def get_htmx_choice(self) -> bool:
return UIFormatter.confirm("Include HTMX? (via django-htmx)", default=True)
def get_docker_choice(self) -> bool:
return UIFormatter.confirm("Include Docker support? (Dockerfile + docker-compose)", default=True)
def get_vite_choice(self) -> bool:
return UIFormatter.confirm("Include Vite/React? (via django-vite)", default=True)
def get_vue_choice(self) -> bool:
return UIFormatter.confirm("Include Vite/Vue? (via django-vite)", default=True)
def get_pytest_choice(self) -> bool:
return UIFormatter.confirm("Include pytest testing framework? (pytest, coverage)", default=True)
def get_django_q_choice(self) -> bool:
return UIFormatter.confirm("Include Django-Q2? (async task queue)", default=False)
def get_celery_choice(self) -> bool:
return UIFormatter.confirm("Include Celery? (async task queue)", default=False)
def _get_structure_metadata(self, options: StructureOptions) -> Tuple[str, str, list[str], dict]:
"""Helper method to generate metadata dictionary."""
project_dir = options["project_dir"]
@@ -203,6 +227,11 @@ class InputCollector:
database_type=options["database_type"],
use_tailwind=options.get("use_tailwind", False),
use_htmx=options.get("use_htmx", False),
use_docker=options.get("use_docker", False),
use_vite=options.get("use_vite", False),
use_vue=options.get("use_vue", False),
use_pytest=options.get("use_pytest", False),
use_django_q=options.get("use_django_q", False),
predefined_structure=options["predefined"],
unified_structure=options["unified"],
single_structure=options["single"],
@@ -264,6 +293,24 @@ def get_user_input() -> Tuple[str, str, str, list, dict]:
use_tailwind = collector.get_tailwind_choice()
use_htmx = collector.get_htmx_choice()
# Step 4: Docker
use_docker = collector.get_docker_choice()
# Step 5: Vite/React
use_vite = collector.get_vite_choice()
# Step 6: Vite/Vue
use_vue = collector.get_vue_choice()
# Step 7: pytest
use_pytest = collector.get_pytest_choice()
# Step 8: Django-Q2
use_django_q = collector.get_django_q_choice()
# Step 9: Celery
use_celery = collector.get_celery_choice()
# Step 3: Django Apps (Standard only)
nested = False
nested_dir = None
@@ -289,6 +336,12 @@ def get_user_input() -> Tuple[str, str, str, list, dict]:
database_type=database_type,
use_tailwind=use_tailwind,
use_htmx=use_htmx,
use_docker=use_docker,
use_vite=use_vite,
use_vue=use_vue,
use_pytest=use_pytest,
use_django_q=use_django_q,
use_celery=use_celery,
)
return project_dir, project_name, app_names[0], app_names, metadata.to_dict()
else:
@@ -302,6 +355,12 @@ def get_user_input() -> Tuple[str, str, str, list, dict]:
use_database_url=use_database_url,
use_tailwind=use_tailwind,
use_htmx=use_htmx,
use_docker=use_docker,
use_vite=use_vite,
use_vue=use_vue,
use_pytest=use_pytest,
use_django_q=use_django_q,
use_celery=use_celery,
use_github=use_github,
use_gitlab=use_gitlab,
)
@@ -340,6 +399,24 @@ def confirm_setup(project_dir: str, project_name: str, app_names: list, metadata
use_htmx = "Yes" if metadata.get("use_htmx", False) else "No"
console.print(f"[{UIColors.HIGHLIGHT}]HTMX:[/{UIColors.HIGHLIGHT}] {use_htmx}")
use_docker = "Yes" if metadata.get("use_docker", False) else "No"
console.print(f"[{UIColors.HIGHLIGHT}]Docker:[/{UIColors.HIGHLIGHT}] {use_docker}")
use_vite = "Yes" if metadata.get("use_vite", False) else "No"
console.print(f"[{UIColors.HIGHLIGHT}]Vite/React:[/{UIColors.HIGHLIGHT}] {use_vite}")
use_pytest = "Yes" if metadata.get("use_pytest", False) else "No"
console.print(f"[{UIColors.HIGHLIGHT}]pytest:[/{UIColors.HIGHLIGHT}] {use_pytest}")
use_vue = "Yes" if metadata.get("use_vue", False) else "No"
console.print(f"[{UIColors.HIGHLIGHT}]Vite/Vue:[/{UIColors.HIGHLIGHT}] {use_vue}")
use_django_q = "Yes" if metadata.get("use_django_q", False) else "No"
console.print(f"[{UIColors.HIGHLIGHT}]Django-Q2:[/{UIColors.HIGHLIGHT}] {use_django_q}")
use_celery = "Yes" if metadata.get("use_celery", False) else "No"
console.print(f"[{UIColors.HIGHLIGHT}]Celery:[/{UIColors.HIGHLIGHT}] {use_celery}")
console.print()
UIFormatter.print_separator()
console.print()
+5
View File
@@ -46,6 +46,11 @@ class DjangoHelper:
"database_type": metadata.get("database_type", "postgresql"),
"use_tailwind": metadata.get("use_tailwind", False),
"use_htmx": metadata.get("use_htmx", False),
"use_vite": metadata.get("use_vite", False),
"use_vue": metadata.get("use_vue", False),
"use_pytest": metadata.get("use_pytest", False),
"use_django_q": metadata.get("use_django_q", False),
"use_celery": metadata.get("use_celery", False),
}
dev_context = {"secret_key": secret_key}
Generated
+1 -1
View File
@@ -43,7 +43,7 @@ wheels = [
[[package]]
name = "djinitx"
version = "0.3.3"
version = "0.3.4"
source = { editable = "." }
dependencies = [
{ name = "questionary" },