Merge branch 'main' into dependabot/github_actions/actions/setup-python-6

This commit is contained in:
sankalp authored and GitHub committed 2025-10-29 12:37:57 +05:45
commit 7f26652102
19 files changed
+80 -152

No files matched your search

+4 -1
View File
@@ -4,6 +4,8 @@ updates:
directory: "/"
schedule:
interval: "weekly"
target-branch: "main"
rebase-strategy: "auto"
labels:
- "dependencies"
- "github-actions"
@@ -12,8 +14,9 @@ updates:
directory: "/"
schedule:
interval: "weekly"
target-branch: "main"
rebase-strategy: "auto"
labels:
- "dependencies"
- "python"
open-pull-requests-limit: 10
-1
View File
@@ -44,4 +44,3 @@ jobs:
- name: Test console script entry point
run: |
djinit --help
-1
View File
@@ -31,4 +31,3 @@ jobs:
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
+2 -2
View File
@@ -23,6 +23,7 @@ jobs:
steps.metadata.outputs.update-type == 'version-update:semver-patch'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN || secrets.GITHUB_TOKEN }}
script: |
await github.pulls.createReview({
owner: context.repo.owner,
@@ -37,6 +38,5 @@ jobs:
steps.metadata.outputs.update-type == 'version-update:semver-patch'
uses: peter-evans/enable-pull-request-automerge@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN || secrets.GITHUB_TOKEN }}
merge-method: squash
-1
View File
@@ -27,4 +27,3 @@ jobs:
- name: Run pre-commit
run: |
pre-commit run --all-files
-1
View File
@@ -18,4 +18,3 @@ repos:
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
-2
View File
@@ -22,8 +22,6 @@ ignore = [
"__init__.py" = ["F401"] # imported but unused
[format]
docstring-code-format = true
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
-1
View File
@@ -17,7 +17,6 @@ setup(
"rich>=14.2.0",
"jinja2>=3.1.0",
# "django<=5.2.7",
],
entry_points={"console_scripts": ["djinit=src.generator:main"]},
classifiers=[
+10 -16
View File
@@ -5,7 +5,6 @@ Handles creation of Django apps and updating settings.
import os
import subprocess
import sys
from typing import Optional
from src.scripts.console_ui import UIFormatter
@@ -54,23 +53,18 @@ class AppManager:
def _create_django_app(self) -> bool:
# Detect project structure
is_nested, nested_dir, apps_base_dir = self._detect_project_structure()
# manage.py is always in the project root (current_dir), not in the project directory
manage_py_path = os.path.join(self.current_dir, "manage.py")
if not os.path.exists(manage_py_path):
UIFormatter.print_error("Could not find manage.py file in project root")
return False
# Create app in the correct location
with change_cwd(apps_base_dir):
UIFormatter.print_info(f"Creating app '{self.app_name}' in directory: {os.getcwd()}")
result = subprocess.run(
["django-admin", "startapp", self.app_name],
capture_output=True,
text=True,
check=True
)
subprocess.run(["django-admin", "startapp", self.app_name], capture_output=True, text=True, check=True)
UIFormatter.print_success(f"Created Django app '{self.app_name}' in {apps_base_dir}")
return True
@@ -93,20 +87,20 @@ class AppManager:
# Detect project structure to determine the correct app module path
is_nested, nested_dir, apps_base_dir = self._detect_project_structure()
# Determine the correct module path for the app
if is_nested and nested_dir:
app_module_path = f"{nested_dir}.{self.app_name}"
else:
app_module_path = self.app_name
UIFormatter.print_info(f"App module path: {app_module_path}")
# Check if app is already in USER_DEFINED_APPS section specifically
lines = content.split("\n")
in_user_apps = False
app_already_exists = False
for line in lines:
if "USER_DEFINED_APPS" in line and "=" in line:
in_user_apps = True
@@ -197,7 +191,7 @@ class AppManager:
# Step 2: try to infer nested_dir from USER_DEFINED_APPS in base.py
try:
if settings_base_path and os.path.exists(settings_base_path):
with open(settings_base_path, "r") as f:
with open(settings_base_path) as f:
base_content = f.read()
# Find lines inside USER_DEFINED_APPS and extract first dotted app
in_user_apps = False
@@ -209,7 +203,7 @@ class AppManager:
break
if in_user_apps:
line_stripped = line.strip().strip(",")
if line_stripped.startswith("\"") or line_stripped.startswith("'"):
if line_stripped.startswith('"') or line_stripped.startswith("'"):
app_label = line_stripped.strip("\"'")
if "." in app_label:
prefix = app_label.split(".", 1)[0]
@@ -243,7 +237,7 @@ class AppManager:
# Detect project structure to get correct app path
is_nested, nested_dir, apps_base_dir = self._detect_project_structure()
app_path = os.path.join(apps_base_dir, self.app_name)
# Create serializers.py
context = {"app_name": self.app_name}
serializers_content = template_engine.render_template("serializers.j2", context)
+2 -2
View File
@@ -25,7 +25,7 @@ class CICDConfigGenerator:
create_file_with_content(
workflow_file,
workflow_content,
f"Created Github Actions workflow ({workflow_file})",
"Created Github Actions workflow (ci.yml)",
)
return True
@@ -36,6 +36,6 @@ class CICDConfigGenerator:
create_file_with_content(
".gitlab-ci.yml",
gitlab_ci_content,
f"Created GitLab CI configuration (.gitlab-ci.yml)",
"Created GitLab CI configuration (.gitlab-ci.yml)",
)
return True
+3 -9
View File
@@ -26,9 +26,7 @@ def parse_arguments() -> argparse.Namespace:
secret_parser.add_argument("--length", type=int, default=50, help="Length of each secret key")
# App command
app_parser = subparsers.add_parser(
"app", help="Create one or more Django apps (comma or space separated)"
)
app_parser = subparsers.add_parser("app", help="Create one or more Django apps (comma or space separated)")
app_parser.add_argument(
"app_name",
nargs="+",
@@ -132,15 +130,11 @@ def handle_app_command(args: argparse.Namespace) -> None:
instructions = Text()
instructions.append("🚀 Next Steps:\n", style=UIColors.ACCENT)
instructions.append(
"1. The app(s) have been added to INSTALLED_APPS in settings/base.py\n"
)
instructions.append("1. The app(s) have been added to INSTALLED_APPS in settings/base.py\n")
instructions.append("2. Create your models in each app's models.py\n")
instructions.append("3. Run migrations: python manage.py makemigrations\n")
instructions.append("4. Apply migrations: python manage.py migrate\n")
instructions.append(
"5. Create views, serializers and routes(URLs) for your app(s)\n"
)
instructions.append("5. Create views, serializers and routes(URLs) for your app(s)\n")
console.print(Panel(instructions, title="💡 What's Next", border_style="green"))
UIFormatter.print_info("")
+41 -98
View File
@@ -3,22 +3,23 @@ Console UI utilities for djinit.
Provides enhanced styling, formatting, and user interface components.
"""
from typing import List, Optional, Dict, Any
from contextlib import contextmanager
from typing import Any, Dict, List, Optional
from rich import box
from rich.console import Console
from rich.panel import Panel
from rich.progress import (
BarColumn,
Progress,
SpinnerColumn,
BarColumn,
TaskProgressColumn,
TextColumn,
TimeRemainingColumn,
)
from rich.syntax import Syntax
from rich.text import Text
from rich.panel import Panel
from rich.table import Table
from rich import box
from contextlib import contextmanager
from rich.text import Text
console = Console()
@@ -65,37 +66,25 @@ class UIFormatter:
@staticmethod
def print_success(message: str, icon: str = Icons.SUCCESS):
console.print(
f"[{UIColors.SUCCESS}]{icon}[/{UIColors.SUCCESS}] [bold]{message}[/bold]"
)
console.print(f"[{UIColors.SUCCESS}]{icon}[/{UIColors.SUCCESS}] [bold]{message}[/bold]")
@staticmethod
def print_error(
message: str, icon: str = Icons.ERROR, details: Optional[str] = None
):
def print_error(message: str, icon: str = Icons.ERROR, details: Optional[str] = None):
"""Print error message with optional details"""
console.print(
f"[{UIColors.ERROR}]{icon}[/{UIColors.ERROR}] [bold]{message}[/bold]"
)
console.print(f"[{UIColors.ERROR}]{icon}[/{UIColors.ERROR}] [bold]{message}[/bold]")
if details:
console.print(f" [dim]{details}[/dim]")
@staticmethod
def print_warning(message: str, icon: str = Icons.WARNING):
console.print(
f"[{UIColors.WARNING}]{icon}[/{UIColors.WARNING}] [bold]{message}[/bold]"
)
console.print(f"[{UIColors.WARNING}]{icon}[/{UIColors.WARNING}] [bold]{message}[/bold]")
@staticmethod
def print_info(message: str, icon: str = Icons.INFO):
console.print(
f"[{UIColors.INFO}]{icon}[/{UIColors.INFO}] [bold]{message}[/bold]"
)
console.print(f"[{UIColors.INFO}]{icon}[/{UIColors.INFO}] [bold]{message}[/bold]")
@staticmethod
def print_step(
step_number: int, total_steps: int, description: str, icon: str = ""
):
def print_step(step_number: int, total_steps: int, description: str, icon: str = ""):
"""Print step information with enhanced progress indicator"""
icon_str = f"{icon} " if icon else ""
percentage = int((step_number / total_steps) * 100)
@@ -116,9 +105,7 @@ class UIFormatter:
)
@staticmethod
def create_live_progress(
description: str = "Setup Progress", total_steps: int = 100
):
def create_live_progress(description: str = "Setup Progress", total_steps: int = 100):
"""Create a live progress display that updates in place"""
progress = Progress(
SpinnerColumn(),
@@ -152,10 +139,8 @@ class UIFormatter:
@staticmethod
def print_feature_list(features: List[str], icon: str = Icons.STAR):
"""Print a styled feature list with custom icons"""
for i, feature in enumerate(features, 1):
console.print(
f"[{UIColors.ACCENT}] {icon}[/{UIColors.ACCENT}] [bold]{feature}[/bold]"
)
for _i, feature in enumerate(features, 1):
console.print(f"[{UIColors.ACCENT}] {icon}[/{UIColors.ACCENT}] [bold]{feature}[/bold]")
@staticmethod
def print_numbered_list(items: List[str], start: int = 1):
@@ -166,9 +151,7 @@ class UIFormatter:
console.print(f" [dim]{prefix}[/dim] [dim]{i}.[/dim] {item}")
@staticmethod
def print_tree_item(
text: str, is_last: bool = False, indent: int = 0, icon: str = ""
):
def print_tree_item(text: str, is_last: bool = False, indent: int = 0, icon: str = ""):
"""Print a tree-style item"""
prefix = "└─" if is_last else "├─"
icon_str = f"{icon} " if icon else ""
@@ -183,15 +166,11 @@ class UIFormatter:
line_numbers: bool = True,
):
"""Print code with syntax highlighting and customization"""
syntax = Syntax(
code, language, theme=theme, line_numbers=line_numbers, word_wrap=True
)
syntax = Syntax(code, language, theme=theme, line_numbers=line_numbers, word_wrap=True)
console.print(syntax)
@staticmethod
def print_panel(
content: str, title: str = "", style: str = "cyan", border_style: str = "blue"
):
def print_panel(content: str, title: str = "", style: str = "cyan", border_style: str = "blue"):
"""Print content in a styled panel"""
panel = Panel(
content,
@@ -208,9 +187,7 @@ class UIFormatter:
if not data:
return
table = Table(
title=title, box=box.ROUNDED, show_header=True, header_style="bold cyan"
)
table = Table(title=title, box=box.ROUNDED, show_header=True, header_style="bold cyan")
# Add columns from first row
for key in data[0].keys():
@@ -245,9 +222,7 @@ class UIFormatter:
style=UIColors.MUTED,
)
welcome_text.append(" Repository: ", style=UIColors.MUTED)
welcome_text.append(
"https://github.com/S4NKALP/djinit\n", style="blue underline"
)
welcome_text.append("https://github.com/S4NKALP/djinit\n", style="blue underline")
welcome_text.append(" License: MIT\n", style=UIColors.MUTED)
return welcome_text
@@ -266,63 +241,39 @@ class UIFormatter:
if success:
# Success header
console.print(f"[bold green]{'═' * 70}[/bold green]")
console.print(
f"[bold green]{f'{Icons.PARTY} SETUP COMPLETE! {Icons.PARTY}'.center(70)}[/bold green]"
)
console.print(f"[bold green]{f'{Icons.PARTY} SETUP COMPLETE! {Icons.PARTY}'.center(70)}[/bold green]")
console.print(f"[bold green]{'═' * 70}[/bold green]\n")
# Duration if provided
if duration:
console.print(
f"[dim]{Icons.CLOCK} Completed in {duration:.2f} seconds[/dim]\n"
)
console.print(f"[dim]{Icons.CLOCK} Completed in {duration:.2f} seconds[/dim]\n")
# Project details
console.print(f"[bold cyan]{Icons.TARGET} Project Details:[/bold cyan]")
console.print(" [dim]│[/dim]")
console.print(
f" [dim]├─[/dim] [bold]Directory:[/bold] [white]{project_dir}[/white]"
)
console.print(
f" [dim]├─[/dim] [bold]Project:[/bold] [white]{project_name}[/white]"
)
console.print(
f" [dim]└─[/dim] [bold]Apps:[/bold] [white]{', '.join(app_names)}[/white]"
)
console.print(f" [dim]├─[/dim] [bold]Directory:[/bold] [white]{project_dir}[/white]")
console.print(f" [dim]├─[/dim] [bold]Project:[/bold] [white]{project_name}[/white]")
console.print(f" [dim]└─[/dim] [bold]Apps:[/bold] [white]{', '.join(app_names)}[/white]")
console.print()
# Next steps
console.print(f"[bold cyan]{Icons.ROCKET} Next Steps:[/bold cyan]")
console.print(" [dim]│[/dim]")
console.print(" [dim]├─[/dim] [dim]1.[/dim] Navigate to your project directory")
console.print(" [dim]├─[/dim] [dim]2.[/dim] Set environment variables in .env file")
console.print(" [dim]├─[/dim] [dim]3.[/dim] Run migrations: [cyan]python manage.py migrate[/cyan]")
console.print(
f" [dim]├─[/dim] [dim]1.[/dim] Navigate to your project directory"
)
console.print(
f" [dim]├─[/dim] [dim]2.[/dim] Set environment variables in .env file"
)
console.print(
f" [dim]├─[/dim] [dim]3.[/dim] Run migrations: [cyan]python manage.py migrate[/cyan]"
)
console.print(
f" [dim]├─[/dim] [dim]4.[/dim] Create superuser: [cyan]python manage.py createsuperuser[/cyan]"
)
console.print(
f" [dim]└─[/dim] [dim]5.[/dim] Start server: [cyan]python manage.py runserver[/cyan]"
" [dim]├─[/dim] [dim]4.[/dim] Create superuser: [cyan]python manage.py createsuperuser[/cyan]"
)
console.print(" [dim]└─[/dim] [dim]5.[/dim] Start server: [cyan]python manage.py runserver[/cyan]")
console.print()
# Useful URLs
console.print(f"[bold cyan]{Icons.LINK} Useful URLs:[/bold cyan]")
console.print(" [dim]│[/dim]")
console.print(
f" [dim]├─[/dim] Admin: [blue]http://localhost:8000/admin/[/blue]"
)
console.print(
f" [dim]├─[/dim] API Docs: [blue]http://localhost:8000/docs/[/blue]"
)
console.print(
f" [dim]└─[/dim] API Schema: [blue]http://localhost:8000/schema/[/blue]"
)
console.print(" [dim]├─[/dim] Admin: [blue]http://localhost:8000/admin/[/blue]")
console.print(" [dim]├─[/dim] API Docs: [blue]http://localhost:8000/docs/[/blue]")
console.print(" [dim]└─[/dim] API Schema: [blue]http://localhost:8000/schema/[/blue]")
console.print()
console.print(f"[bold green]{'═' * 70}[/bold green]\n")
@@ -336,22 +287,18 @@ class UIFormatter:
console.print("[red]The setup process encountered an error.[/red]\n")
# Troubleshooting
console.print(
f"[bold yellow]{Icons.MAGNIFIER} Troubleshooting Tips:[/bold yellow]"
)
console.print(f"[bold yellow]{Icons.MAGNIFIER} Troubleshooting Tips:[/bold yellow]")
console.print(" [dim]│[/dim]")
console.print(f" [dim]├─[/dim] Check the error messages above")
console.print(f" [dim]├─[/dim] Ensure you have write permissions")
console.print(f" [dim]├─[/dim] Verify Django is installed correctly")
console.print(f" [dim]└─[/dim] Try running with elevated permissions")
console.print(" [dim]├─[/dim] Check the error messages above")
console.print(" [dim]├─[/dim] Ensure you have write permissions")
console.print(" [dim]├─[/dim] Verify Django is installed correctly")
console.print(" [dim]└─[/dim] Try running with elevated permissions")
console.print()
console.print(f"[bold red]{'═' * 70}[/bold red]\n")
@staticmethod
def create_progress_display(
description: str = "Processing", total_steps: int = 100
):
def create_progress_display(description: str = "Processing", total_steps: int = 100):
"""Create a comprehensive live progress display"""
progress = Progress(
SpinnerColumn(),
@@ -374,11 +321,7 @@ class UIFormatter:
def confirm(prompt: str, default: bool = True) -> bool:
"""Display a confirmation prompt"""
default_str = "Y/n" if default else "y/N"
response = (
console.input(f"[bold cyan]?[/bold cyan] {prompt} [{default_str}]: ")
.strip()
.lower()
)
response = console.input(f"[bold cyan]?[/bold cyan] {prompt} [{default_str}]: ").strip().lower()
if not response:
return default
+3 -3
View File
@@ -77,7 +77,7 @@ tmp_upload_dir = None
create_file_with_content(
"gunicorn_config.py",
gunicorn_config_content,
f"Created gunicorn_config.py with Gunicorn configuration",
"Created gunicorn_config.py with Gunicorn configuration",
)
return True
@@ -88,7 +88,7 @@ tmp_upload_dir = None
create_file_with_content(
"runtime.txt",
runtime_content,
f"Created runtime.txt with Python version specification",
"Created runtime.txt with Python version specification",
)
return True
@@ -113,6 +113,6 @@ cmd = "gunicorn {self.project_name}.wsgi:application --bind 0.0.0.0:$PORT"
create_file_with_content(
"nixpacks.toml",
nixpacks_content,
f"Created nixpacks.toml for Nixpacks deployment",
"Created nixpacks.toml for Nixpacks deployment",
)
return True
+1 -1
View File
@@ -53,7 +53,7 @@ class FileManager:
with change_cwd(self.project_root):
context = {
"project_name": self.project_name,
"use_database_url": self.metadata.get("use_database_url", True)
"use_database_url": self.metadata.get("use_database_url", True),
}
env_content = template_engine.render_template("env_sample.j2", context)
return create_file_with_content(
+9 -5
View File
@@ -203,8 +203,12 @@ class InputCollector:
"""Ask whether to create nested Django apps and get directory name if yes."""
UIFormatter.print_separator()
console.print(f"\n[{UIColors.INFO}]Apps Layout[/{UIColors.INFO}]\n")
console.print(f"[{UIColors.MUTED}]Do you want to place apps inside a package directory (e.g., 'apps/')?[/{UIColors.MUTED}]")
console.print(f"[{UIColors.MUTED}]If yes, we'll create that directory as a Python package and generate apps inside it.[/{UIColors.MUTED}]")
console.print(
f"[{UIColors.MUTED}]Do you want to place apps inside a package directory (e.g., 'apps/')?[/{UIColors.MUTED}]"
)
console.print(
f"[{UIColors.MUTED}]If yes, we'll create that directory as a Python package and generate apps inside it.[/{UIColors.MUTED}]"
)
console.print()
console.print(f"[{UIColors.HIGHLIGHT}]Nested Django apps? (y/N):[/{UIColors.HIGHLIGHT}] ", end="")
@@ -270,7 +274,7 @@ class CharReader:
# Fallback
try:
return msvcrt.getch().lower()
except:
except Exception:
return ""
@staticmethod
@@ -298,7 +302,7 @@ def get_user_input() -> Tuple[str, str, str, list, dict]:
project_dir = collector.get_validated_input(
"Enter project directory name", validate_project_name, "project directory name"
)
# Check if directory exists and ask for alternative
while os.path.exists(project_dir):
UIFormatter.print_error(f"Directory '{project_dir}' already exists.")
@@ -364,7 +368,7 @@ def confirm_setup(project_dir: str, project_name: str, app_names: list, metadata
console.print(f"[{UIColors.HIGHLIGHT}]CI/CD:[/{UIColors.HIGHLIGHT}] {cicd_choices}")
# Show database configuration
db_config = "DATABASE_URL" if metadata.get('use_database_url', True) else "Individual parameters"
db_config = "DATABASE_URL" if metadata.get("use_database_url", True) else "Individual parameters"
console.print(f"[{UIColors.HIGHLIGHT}]Database Config:[/{UIColors.HIGHLIGHT}] {db_config}")
console.print()
+2 -3
View File
@@ -3,6 +3,7 @@ Project management for djinit.
Handles creation of Django projects and apps.
"""
import importlib.util
import os
import subprocess
import sys
@@ -65,9 +66,7 @@ class ProjectManager:
return True
def _ensure_django_installed(self) -> None:
try:
import django
except ImportError:
if importlib.util.find_spec("django") is None:
subprocess.run(
[sys.executable, "-m", "pip", "install", "--upgrade", "django", "-q"],
check=True,
+1 -3
View File
@@ -27,9 +27,7 @@ def generate_multiple_keys(count: int = 3, length: int = 50) -> list[str]:
def display_secret_keys(keys: list[str]) -> None:
table = Table(
title="🔐 Django Secret Keys", show_header=True, header_style="bold blue"
)
table = Table(title="🔐 Django Secret Keys", show_header=True, header_style="bold blue")
table.add_column("Environment", style="cyan", no_wrap=True)
table.add_column("Secret Key", style="dim")
+1 -1
View File
@@ -20,4 +20,4 @@ EMAIL_HOST=
EMAIL_PORT=
EMAIL_USE_TLS=
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
EMAIL_HOST_PASSWORD=
+1 -1
View File
@@ -13,4 +13,4 @@ router = DefaultRouter()
# router.register(r'{{ app_name }}', views.{{ app_name|title }}ViewSet)
# Get URL patterns
urlpatterns = router.urls
urlpatterns = router.urls