feat: add file and project templates for djinit

This commit is contained in:
sankalp committed 2025-10-28 22:55:23 +05:45
1 parent d3729de3a0
commit 172cfd2a22
4 files changed
+827 -1

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
"""
App management for Django project setup.
App management for djinit.
Handles creation of Django apps and updating settings.
"""
+412
View File
@@ -0,0 +1,412 @@
"""
File management utilities for djinit.
Handles creation and modification of project files.
"""
import os
from src.utils import change_cwd, create_file_with_content
class FileManager:
def __init__(self, project_root: str, project_name: str, app_names: list):
self.project_root = project_root
self.project_name = project_name
self.app_names = app_names
self.project_configs = os.path.join(project_root, project_name)
self.settings_folder = os.path.join(self.project_configs, "settings")
self.settings_file = os.path.join(self.project_configs, "settings.py")
def create_gitignore(self) -> bool:
with change_cwd(self.project_root):
gitignore_content = [
"# Python",
"*.py[cod]",
"*$py.class",
"*.so",
"__pycache__/",
"*.egg-info/",
"dist/",
"build/",
".eggs/",
"*.egg",
"# Django",
"*.log",
"local_settings.py",
"db.sqlite3",
"db.sqlite3-journal",
"*.sqlite3",
"# Environment",
".env",
".venv",
"env/",
"venv/",
"ENV/",
"env.bak/",
"venv.bak/",
"# IDEs",
".vscode/",
".idea/",
"*.swp",
"*.swo",
"*~",
".project",
".pydevproject",
".settings/",
".ropeproject/",
"# OS",
".DS_Store",
"*.DS_Store",
"Thumbs.db",
"desktop.ini",
"# Django specific",
"media/",
"staticfiles/",
"staticfiles/",
"static_root/",
"/static/",
"# Testing",
".coverage",
"htmlcov/",
".pytest_cache/",
".tox/",
"coverage.xml",
"*.cover",
".hypothesis/",
".cache",
"# Logs",
"logs/",
"*.log",
"npm-debug.log*",
"yarn-debug.log*",
"yarn-error.log*",
"# Node modules (if using frontend tools)",
"node_modules/",
"# Secret keys",
"secret.txt",
"secrets.json",
"# Jupyter Notebook",
".ipynb_checkpoints",
"# Backup files",
"*.bak",
"*.tmp",
"*~",
"# Database",
"*.db",
"*.sql",
"# Cloud",
".cloud",
]
return create_file_with_content(
".gitignore",
"\n".join(gitignore_content) + "\n",
"Created .gitignore file",
)
def create_requirements(self) -> bool:
with change_cwd(self.project_root):
requirements = [
"Django>=5.2.7",
"python-dotenv>=1.1.1",
"django-jazzmin>=3.0.1",
"djangorestframework>=3.16.1",
"djangorestframework_simplejwt>=5.5.1",
"drf-spectacular>=0.28.0",
"django-cors-headers>=4.9.0",
"whitenoise>=6.8.2",
"psycopg2-binary>=2.9.9", # For PostgreSQL support
"gunicorn>=21.2.0", # For production deployment
]
return create_file_with_content(
"requirements.txt",
"\n".join(requirements) + "\n",
"Created requirements.txt with Django dependencies",
)
def create_readme(self) -> bool:
with change_cwd(self.project_root):
readme_content = f"""# {self.project_name}
A Django project created with django-setup.
## Features
- Modern project structure with environment-specific settings
- Pre-configured REST API with JWT authentication
- Essential dependencies and utilities
- Production-ready configuration
## Setup
1. Install dependencies:
```bash
pip install -r requirements.txt
```
2. Set environment variables in `.env` file
3. Run migrations:
```bash
python manage.py migrate
```
4. Create superuser:
```bash
python manage.py createsuperuser
```
5. Start development server:
```bash
python manage.py runserver
```
## Project Structure
```
{self.project_name}/
├── {self.project_name}/
│ ├── settings/
│ │ ├── base.py
│ │ ├── development.py
│ │ └── production.py
│ ├── urls.py
│ ├── wsgi.py
│ └── asgi.py
{"".join([f"├── {app}/\n" for app in self.app_names])}├── manage.py
├── requirements.txt
├── pyproject.toml
├── .env.sample
└── README.md
```
## API Documentation
When running in development mode, API documentation is available at:
- Swagger UI: http://localhost:8000/docs/
- Schema: http://localhost:8000/schema/
"""
return create_file_with_content(
"README.md",
readme_content,
"Created README.md file",
)
def create_env_file(self) -> bool:
"""Create .env.sample file with environment variables."""
with change_cwd(self.project_root):
env_content = f"""# Django settings
DJANGO_SETTINGS_MODULE={self.project_name}.settings.development
SECRET_KEY= your-secret-key
ALLOWED_HOSTS=api.your-domain.com,www.your-domain.com
# Database
DB_NAME=
DB_USER=
DB_PASSWORD=
DB_HOST=
DB_PORT=
"""
return create_file_with_content(
".env.sample",
env_content,
"Created .env.sample file with generated secret key",
)
def create_pyproject_toml(self, metadata: dict) -> bool:
with change_cwd(self.project_root):
pyproject_content = f"""[project]
name = "{metadata["package_name"]}"
version = "0.1.0"
description = ""
readme = "README.md"
requires-python = ">=3.13"
license = {{ text = "MIT }}
dependencies = [
"django>=5.2.7",
"python-dotenv>=1.1.1",
"django-jazzmin>=3.0.1",
"djangorestframework>=3.16.1",
"djangorestframework-simplejwt>=5.5.1",
"drf-spectacular>=0.28.0",
"django-cors-headers>=4.9.0",
"whitenoise>=6.8.2",
"psycopg2-binary>=2.9.9",
"gunicorn>=21.2.0",
]
[tool.ruff]
line-length = 88
target-version = "py38"
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
]
ignore = [
"E501", # line too long, handled by formatter
"B008", # do not perform function calls in argument defaults
]
[tool.ruff.lint.isort]
known-first-party = ["{self.project_name}"]
"""
return create_file_with_content(
"pyproject.toml",
pyproject_content,
"Created pyproject.toml file for uv",
)
def create_app_urls(self) -> bool:
for app_name in self.app_names:
app_path = os.path.join(self.project_root, app_name)
with change_cwd(app_path):
urls_content = f"""from django.urls import path
from . import views
app_name = '{app_name}'
urlpatterns = [
# Add your URL patterns here
# path('', views.index, name='index'),
]
"""
create_file_with_content(
"urls.py",
urls_content,
f"Created {app_name}/urls.py",
should_format=True,
)
return True
def update_project_urls(self) -> bool:
with change_cwd(self.project_configs):
# Generate URL includes for all apps
app_urls = "\n ".join([f'path("", include("{app_name}.urls")),' for app_name in self.app_names])
urls_content = f"""from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from drf_spectacular.views import (
SpectacularAPIView,
SpectacularSwaggerView,
)
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenBlacklistView,
)
urlpatterns = [
path("admin/", admin.site.urls),
# JWT tokens
path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
path("token/blacklist/", TokenBlacklistView.as_view(), name="token_blacklist"),
# App URLs
{app_urls}
]
# Don't show schema in production
if settings.DEBUG:
urlpatterns += [
path("schema/", SpectacularAPIView.as_view(), name="schema"),
path("docs/", SpectacularSwaggerView.as_view(url_name="schema")),
]
"""
result = create_file_with_content(
"urls.py",
urls_content,
"Updated project urls.py with comprehensive URL configuration",
should_format=True,
)
return result
def update_wsgi_file(self) -> bool:
with change_cwd(self.project_configs):
wsgi_content = """import os
from dotenv import load_dotenv
from django.core.wsgi import get_wsgi_application
load_dotenv()
os.environ.setdefault("DJANGO_SETTINGS_MODULE", os.getenv("DJANGO_SETTINGS_MODULE"))
application = get_wsgi_application()
"""
return create_file_with_content(
"wsgi.py",
wsgi_content,
"Updated wsgi.py with custom configuration",
should_format=True,
)
def update_asgi_file(self) -> bool:
with change_cwd(self.project_configs):
asgi_content = """import os
from dotenv import load_dotenv
from django.core.asgi import get_asgi_application
load_dotenv()
os.environ.setdefault("DJANGO_SETTINGS_MODULE", os.getenv("DJANGO_SETTINGS_MODULE"))
application = get_asgi_application()
"""
return create_file_with_content(
"asgi.py",
asgi_content,
"Updated asgi.py with custom configuration",
should_format=True,
)
def update_manage_py(self) -> bool:
"""Update manage.py with custom configuration."""
with change_cwd(self.project_root):
manage_content = """#!/usr/bin/env python
\"\"\"Django's command-line utility for administrative tasks.\"\"\"
import os
import sys
from dotenv import load_dotenv
def main():
\"\"\"Run administrative tasks.\"\"\"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", os.getenv("DJANGO_SETTINGS_MODULE"))
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
load_dotenv()
main()
"""
return create_file_with_content(
"manage.py",
manage_content,
"Updated manage.py with custom configuration",
should_format=True,
)
+105
View File
@@ -0,0 +1,105 @@
"""
Project management for djinit.
Handles creation of Django projects and apps.
"""
import os
import subprocess
import sys
from src.scripts.console_ui import UIFormatter
from src.utils import change_cwd
class ProjectManager:
def __init__(self, project_dir: str, project_name: str, app_names: list):
self.project_dir = project_dir
self.project_name = project_name
self.app_names = app_names
self.project_root = os.path.join(os.getcwd(), project_dir)
def create_project(self) -> bool:
# Check if project already exists
if os.path.exists(self.project_root):
UIFormatter.print_error(f"Directory '{self.project_dir}' already exists.")
UIFormatter.print_info("Please choose a different project directory name or remove the existing directory.")
return False
# Ensure Django is installed
self._ensure_django_installed()
# Create the project directory first
os.makedirs(self.project_root, exist_ok=True)
# Create Django project inside the directory
subprocess.run(
["django-admin", "startproject", self.project_name, self.project_root],
check=True,
)
UIFormatter.print_success(f"Django project '{self.project_name}' created successfully!")
return True
def create_apps(self) -> bool:
with change_cwd(self.project_root):
# Create each app
for app_name in self.app_names:
try:
subprocess.run(
[
sys.executable,
os.path.join(self.project_root, "manage.py"),
"startapp",
app_name,
],
check=True,
)
UIFormatter.print_success(f"Django app '{app_name}' created successfully!")
except subprocess.CalledProcessError as e:
UIFormatter.print_error(f"Failed to create app '{app_name}': {e}")
return False
return True
def _ensure_django_installed(self) -> None:
try:
import django
except ImportError:
UIFormatter.print_info("Django not found, installing...")
subprocess.run(
[sys.executable, "-m", "pip", "install", "--upgrade", "django"],
check=True,
)
UIFormatter.print_success("Django installed successfully!")
def validate_project_structure(self) -> bool:
required_files = [
os.path.join(self.project_root, "manage.py"),
os.path.join(self.project_root, self.project_name, "__init__.py"),
os.path.join(self.project_root, self.project_name, "settings.py"),
os.path.join(self.project_root, self.project_name, "urls.py"),
os.path.join(self.project_root, self.project_name, "wsgi.py"),
os.path.join(self.project_root, self.project_name, "asgi.py"),
]
# Add required files for each app
for app_name in self.app_names:
app_files = [
os.path.join(self.project_root, app_name, "__init__.py"),
os.path.join(self.project_root, app_name, "apps.py"),
os.path.join(self.project_root, app_name, "models.py"),
os.path.join(self.project_root, app_name, "views.py"),
os.path.join(self.project_root, app_name, "admin.py"),
]
required_files.extend(app_files)
missing_files = [file_path for file_path in required_files if not os.path.exists(file_path)]
if missing_files:
UIFormatter.print_error("Project structure validation failed:")
for file_path in missing_files:
UIFormatter.print_error(f" Missing: {file_path}")
return False
UIFormatter.print_success("Project structure validation passed")
return True
+309
View File
@@ -0,0 +1,309 @@
"""
Settings management for djinit.
Handles creation and configuration of Django settings files.
"""
import os
from src.scripts.console_ui import UIFormatter
from src.scripts.secretkey_generator import generate_secret_key
from src.utils import change_cwd, create_file_with_content
class SettingsManager:
def __init__(self, project_root: str, project_name: str, app_names: list):
self.project_root = project_root
self.project_name = project_name
self.app_names = app_names
self.project_configs = os.path.join(project_root, project_name)
self.settings_folder = os.path.join(self.project_configs, "settings")
self.settings_file = os.path.join(self.project_configs, "settings.py")
self.secret_key = generate_secret_key(50)
def create_settings_structure(self) -> bool:
# Change to project configs directory
with change_cwd(self.project_configs):
# Create settings folder
os.makedirs("settings", exist_ok=True)
# Move settings.py to settings/base.py
if os.path.exists(self.settings_file):
os.rename(self.settings_file, os.path.join(self.settings_folder, "base.py"))
# Create __init__.py files
with change_cwd(self.settings_folder):
open("__init__.py", "a").close()
open("development.py", "a").close()
open("production.py", "a").close()
UIFormatter.print_success("Created Django settings folder structure")
return True
def update_base_settings(self) -> bool:
with change_cwd(self.settings_folder):
# Generate app list from app_names
app_list_lines = [f' "{app_name}",' for app_name in self.app_names]
app_list = "\n".join(app_list_lines)
base_content = f'''"""
Common settings shared between development and production environment
"""
import os
from datetime import timedelta
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# Application definition
THIRD_PARTY_APPS = [
"rest_framework",
"rest_framework_simplejwt",
"rest_framework_simplejwt.token_blacklist",
"corsheaders",
"drf_spectacular",
]
USER_DEFINED_APPS = [
{app_list}
]
BUILT_IN_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
INSTALLED_APPS = BUILT_IN_APPS + THIRD_PARTY_APPS + USER_DEFINED_APPS
INSTALLED_APPS.insert(0, "jazzmin")
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "{self.project_name}.urls"
TEMPLATES = [
{{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {{
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
}},
}},
]
WSGI_APPLICATION = "{self.project_name}.wsgi.application"
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
}},
{{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
}},
{{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
}},
{{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
}},
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files and media files
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# REST Framework Settings
REST_FRAMEWORK = {{
"DEFAULT_RENDERER_CLASSES": [
"rest_framework.renderers.JSONRenderer",
],
"DEFAULT_PARSER_CLASSES": [
"rest_framework.parsers.JSONParser",
],
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
"rest_framework.authentication.SessionAuthentication",
),
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
}}
# DRF Spectacular settings
SPECTACULAR_SETTINGS = {{
"TITLE": "{self.project_name} API",
"DESCRIPTION": "API documentation for {self.project_name}",
"VERSION": "1.0.0",
"SERVE_INCLUDE_SCHEMA": False,
"COMPONENT_SPLIT_REQUEST": True,
"SCHEMA_PATH_PREFIX": "/api/",
}}
# JWT Settings
SIMPLE_JWT = {{
"ACCESS_TOKEN_LIFETIME": timedelta(days=1),
"REFRESH_TOKEN_LIFETIME": timedelta(days=3),
"ROTATE_REFRESH_TOKENS": True,
"BLACKLIST_AFTER_ROTATION": True,
}}
# CORS Settings
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
"http://127.0.0.1:3000",
]
CORS_ALLOW_CREDENTIALS = True
# Security Settings
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"
'''
return create_file_with_content(
"base.py",
base_content,
"Updated settings/base.py with comprehensive configuration",
should_format=True,
)
def update_development_settings(self) -> bool:
"""Update development.py with development-specific settings."""
with change_cwd(self.settings_folder):
dev_content = f"""from .base import *
SECRET_KEY = "{self.secret_key}"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ["*"]
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {{
"default": {{
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}}
}}
# Email backend for development
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
# CORS settings for development
CORS_ALLOW_ALL_ORIGINS = True
# Disable HTTPS redirect in development
SECURE_SSL_REDIRECT = False
"""
return create_file_with_content(
"development.py",
dev_content,
"Updated settings/development.py",
should_format=True,
)
def update_production_settings(self) -> bool:
"""Update production.py with production-specific settings."""
with change_cwd(self.settings_folder):
prod_content = """from .base import *
import os
DEBUG = False
SECRET_KEY = os.getenv('SECRET_KEY')
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '').split(',')
# Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.getenv('DB_NAME'),
'USER': os.getenv('DB_USER'),
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': os.getenv('DB_HOST'),
'PORT': os.getenv('DB_PORT'),
}
}
# Email settings
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.getenv('EMAIL_HOST')
EMAIL_PORT = int(os.getenv('EMAIL_PORT', 587))
EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS', 'True').lower() == 'true'
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD')
# Security settings for production
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# Session settings
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# Static files
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
"""
return create_file_with_content(
"production.py",
prod_content,
"Updated settings/production.py",
should_format=True,
)