djinitx 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- djinit/__init__.py +0 -0
- djinit/cli.py +109 -0
- djinit/generator.py +74 -0
- djinit/scripts/__init__.py +0 -0
- djinit/scripts/app_generator.py +254 -0
- djinit/scripts/cicd_config_generator.py +41 -0
- djinit/scripts/command_handler.py +140 -0
- djinit/scripts/console_ui.py +381 -0
- djinit/scripts/deployment_files_generator.py +118 -0
- djinit/scripts/files.py +163 -0
- djinit/scripts/input_handler.py +406 -0
- djinit/scripts/name_validator.py +52 -0
- djinit/scripts/project.py +111 -0
- djinit/scripts/secretkey_generator.py +59 -0
- djinit/scripts/settings.py +86 -0
- djinit/scripts/template_engine.py +80 -0
- djinit/templates/app_urls.j2 +9 -0
- djinit/templates/asgi.j2 +9 -0
- djinit/templates/env_sample.j2 +23 -0
- djinit/templates/github_actions_ci.j2 +109 -0
- djinit/templates/gitignore.j2 +89 -0
- djinit/templates/gitlab_ci.j2 +133 -0
- djinit/templates/justfile.j2 +154 -0
- djinit/templates/manage_py.j2 +25 -0
- djinit/templates/procfile.j2 +20 -0
- djinit/templates/project_urls.j2 +29 -0
- djinit/templates/pyproject_toml.j2 +41 -0
- djinit/templates/readme.j2 +60 -0
- djinit/templates/requirements.j2 +11 -0
- djinit/templates/routes.j2 +16 -0
- djinit/templates/runtime_txt.j2 +1 -0
- djinit/templates/serializers.j2 +13 -0
- djinit/templates/settings_base.j2 +161 -0
- djinit/templates/settings_development.j2 +27 -0
- djinit/templates/settings_production.j2 +51 -0
- djinit/templates/wsgi.j2 +9 -0
- djinit/utils.py +52 -0
- djinitx-0.1.0.dist-info/METADATA +182 -0
- djinitx-0.1.0.dist-info/RECORD +43 -0
- djinitx-0.1.0.dist-info/WHEEL +5 -0
- djinitx-0.1.0.dist-info/entry_points.txt +3 -0
- djinitx-0.1.0.dist-info/licenses/LICENSE +21 -0
- djinitx-0.1.0.dist-info/top_level.txt +1 -0
djinit/__init__.py
ADDED
|
File without changes
|
djinit/cli.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main CLI orchestrator for djinit.
|
|
3
|
+
Coordinates between different managers to create a complete Django project.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from typing import Callable, List, Tuple
|
|
8
|
+
|
|
9
|
+
from djinit.scripts.cicd_config_generator import CICDConfigGenerator
|
|
10
|
+
from djinit.scripts.console_ui import UIFormatter
|
|
11
|
+
from djinit.scripts.deployment_files_generator import DeploymentConfigGenerator
|
|
12
|
+
from djinit.scripts.files import FileManager
|
|
13
|
+
from djinit.scripts.project import ProjectManager
|
|
14
|
+
from djinit.scripts.secretkey_generator import generate_secret_command
|
|
15
|
+
from djinit.scripts.settings import SettingsManager
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Cli:
|
|
19
|
+
def __init__(self, project_dir: str, project_name: str, primary_app: str, app_names: list, metadata: dict):
|
|
20
|
+
self.project_dir = project_dir
|
|
21
|
+
self.project_name = project_name
|
|
22
|
+
self.primary_app = primary_app
|
|
23
|
+
self.app_names = app_names
|
|
24
|
+
self.metadata = metadata
|
|
25
|
+
self.project_root = os.path.join(os.getcwd(), project_dir)
|
|
26
|
+
|
|
27
|
+
# Initialize managers
|
|
28
|
+
self.project_manager = ProjectManager(project_dir, project_name, app_names, metadata)
|
|
29
|
+
self.settings_manager = SettingsManager(self.project_root, project_name, app_names, metadata)
|
|
30
|
+
self.file_manager = FileManager(self.project_root, project_name, app_names, metadata)
|
|
31
|
+
self.deployment_manager = DeploymentConfigGenerator(self.project_root, project_name)
|
|
32
|
+
self.cicd_manager = CICDConfigGenerator(self.project_root, project_name)
|
|
33
|
+
|
|
34
|
+
def run_setup(self) -> bool:
|
|
35
|
+
steps: List[Tuple[str, Callable[[], bool]]] = [
|
|
36
|
+
("Creating Django project", self.project_manager.create_project),
|
|
37
|
+
("Creating Django apps", self.project_manager.create_apps),
|
|
38
|
+
("Validating project structure", self.project_manager.validate_project_structure),
|
|
39
|
+
("Setting up project structure", self.settings_manager.create_settings_structure),
|
|
40
|
+
("Configuring base settings", self.settings_manager.update_base_settings),
|
|
41
|
+
("Configuring development settings", self.settings_manager.update_development_settings),
|
|
42
|
+
("Configuring production settings", self.settings_manager.update_production_settings),
|
|
43
|
+
("Creating utility files", self._create_utility_files),
|
|
44
|
+
("Setting up app URLs", self.file_manager.create_app_urls),
|
|
45
|
+
("Creating app serializers", self.file_manager.create_app_serializers),
|
|
46
|
+
("Creating app routes", self.file_manager.create_app_routes),
|
|
47
|
+
("Configuring comprehensive URLs", self.file_manager.update_project_urls),
|
|
48
|
+
("Updating WSGI configuration", self.file_manager.update_wsgi_file),
|
|
49
|
+
("Updating ASGI configuration", self.file_manager.update_asgi_file),
|
|
50
|
+
("Updating manage.py", self.file_manager.update_manage_py),
|
|
51
|
+
("Creating Justfile", self.deployment_manager.create_justfile),
|
|
52
|
+
("Creating Procfile", self.deployment_manager.create_procfile),
|
|
53
|
+
("Creating runtime.txt", lambda: self.deployment_manager.create_runtime_txt("3.13")),
|
|
54
|
+
("Creating CI/CD pipelines", self._create_cicd_pipelines),
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
total_steps = len(steps)
|
|
58
|
+
success = True
|
|
59
|
+
|
|
60
|
+
# Create live progress display
|
|
61
|
+
progress, task = UIFormatter.create_live_progress(description="Setup Progress", total_steps=total_steps)
|
|
62
|
+
|
|
63
|
+
with progress:
|
|
64
|
+
# Execute each step with progress tracking
|
|
65
|
+
for step_number, (description, step_func) in enumerate(steps, 1):
|
|
66
|
+
result = step_func()
|
|
67
|
+
if not result:
|
|
68
|
+
success = False
|
|
69
|
+
UIFormatter.print_error(f"Step {step_number} failed: {description}")
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
# Update the same progress bar
|
|
73
|
+
progress.update(task, advance=1, description=f"Step {step_number}/{total_steps}")
|
|
74
|
+
|
|
75
|
+
return success
|
|
76
|
+
|
|
77
|
+
def _create_utility_files(self) -> bool:
|
|
78
|
+
utility_steps = [
|
|
79
|
+
self.file_manager.create_gitignore,
|
|
80
|
+
self.file_manager.create_requirements,
|
|
81
|
+
self.file_manager.create_readme,
|
|
82
|
+
self.file_manager.create_env_file,
|
|
83
|
+
lambda: self.file_manager.create_pyproject_toml(self.metadata),
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
for step_func in utility_steps:
|
|
87
|
+
result = step_func()
|
|
88
|
+
if not result:
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
UIFormatter.print_success("Created all utility files successfully!")
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
def _create_cicd_pipelines(self) -> bool:
|
|
95
|
+
if self.metadata.get("use_github_actions", True):
|
|
96
|
+
result = self.cicd_manager.create_github_actions()
|
|
97
|
+
if not result:
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
if self.metadata.get("use_gitlab_ci", True):
|
|
101
|
+
result = self.cicd_manager.create_gitlab_ci()
|
|
102
|
+
if not result:
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
def generate_secret_keys(self) -> bool:
|
|
108
|
+
generate_secret_command()
|
|
109
|
+
return True
|
djinit/generator.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main script entry point for djinit.
|
|
3
|
+
Handles user input validation and orchestrates the setup process.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from djinit.cli import Cli
|
|
11
|
+
from djinit.scripts.command_handler import handle_app_command, handle_secret_command, parse_arguments
|
|
12
|
+
from djinit.scripts.console_ui import UIFormatter, console
|
|
13
|
+
from djinit.scripts.input_handler import confirm_setup, get_user_input
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def clear_screen() -> None:
|
|
17
|
+
try:
|
|
18
|
+
if os.name == "nt":
|
|
19
|
+
subprocess.run("cls", shell=True, check=True) # Windows
|
|
20
|
+
else:
|
|
21
|
+
subprocess.run("clear", shell=True, check=True) # Linux/MacOS
|
|
22
|
+
except subprocess.CalledProcessError:
|
|
23
|
+
# If clearing fails, just print some newlines
|
|
24
|
+
console.print("\n" * 50)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main() -> None:
|
|
28
|
+
# Parse command line arguments
|
|
29
|
+
args = parse_arguments()
|
|
30
|
+
|
|
31
|
+
# Handle secret command
|
|
32
|
+
if args.command == "secret":
|
|
33
|
+
handle_secret_command(args)
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
# Handle app command
|
|
37
|
+
if args.command == "app":
|
|
38
|
+
handle_app_command(args)
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
# Default to setup command
|
|
42
|
+
# Clear screen
|
|
43
|
+
clear_screen()
|
|
44
|
+
|
|
45
|
+
# Display welcome screen
|
|
46
|
+
console.print()
|
|
47
|
+
console.print(UIFormatter.create_welcome_panel())
|
|
48
|
+
console.print()
|
|
49
|
+
|
|
50
|
+
# Get user input
|
|
51
|
+
project_dir, project_name, primary_app, app_names, metadata = get_user_input()
|
|
52
|
+
|
|
53
|
+
# Confirm setup
|
|
54
|
+
if not confirm_setup(project_dir, project_name, app_names, metadata):
|
|
55
|
+
UIFormatter.print_info("Setup cancelled by user.")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Run setup
|
|
59
|
+
console.print()
|
|
60
|
+
UIFormatter.print_info("Starting Django project setup...")
|
|
61
|
+
console.print()
|
|
62
|
+
|
|
63
|
+
django_cli = Cli(project_dir, project_name, primary_app, app_names, metadata)
|
|
64
|
+
success = django_cli.run_setup()
|
|
65
|
+
|
|
66
|
+
# Display completion summary
|
|
67
|
+
UIFormatter.create_summary_panel(project_dir, project_name, app_names, True)
|
|
68
|
+
|
|
69
|
+
# Exit with appropriate code
|
|
70
|
+
sys.exit(0 if success else 1)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""
|
|
2
|
+
App management for djinit.
|
|
3
|
+
Handles creation of Django apps and updating settings.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from djinit.scripts.console_ui import UIFormatter
|
|
11
|
+
from djinit.scripts.template_engine import template_engine
|
|
12
|
+
from djinit.utils import change_cwd, create_file_with_content
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AppManager:
|
|
16
|
+
def __init__(self, app_name: str):
|
|
17
|
+
self.app_name = app_name
|
|
18
|
+
self.current_dir = os.getcwd()
|
|
19
|
+
self.manage_py_path = os.path.join(self.current_dir, "manage.py")
|
|
20
|
+
|
|
21
|
+
def create_app(self) -> bool:
|
|
22
|
+
if not self._is_django_project():
|
|
23
|
+
UIFormatter.print_error(
|
|
24
|
+
"Not in a Django project directory. Please run this command from your Django project root."
|
|
25
|
+
)
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
if self._app_exists():
|
|
29
|
+
UIFormatter.print_error(f"Django app '{self.app_name}' already exists.")
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
if not self._create_django_app():
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
if not self._add_to_installed_apps():
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
if not self._create_additional_files():
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
UIFormatter.print_success(f"Django app '{self.app_name}' created and configured successfully!")
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
def _is_django_project(self) -> bool:
|
|
45
|
+
return os.path.exists(self.manage_py_path)
|
|
46
|
+
|
|
47
|
+
def _app_exists(self) -> bool:
|
|
48
|
+
# Detect project structure to check in correct location
|
|
49
|
+
is_nested, nested_dir, apps_base_dir = self._detect_project_structure()
|
|
50
|
+
app_path = os.path.join(apps_base_dir, self.app_name)
|
|
51
|
+
return os.path.exists(app_path)
|
|
52
|
+
|
|
53
|
+
def _create_django_app(self) -> bool:
|
|
54
|
+
# Detect project structure
|
|
55
|
+
is_nested, nested_dir, apps_base_dir = self._detect_project_structure()
|
|
56
|
+
|
|
57
|
+
# manage.py is always in the project root (current_dir), not in the project directory
|
|
58
|
+
manage_py_path = os.path.join(self.current_dir, "manage.py")
|
|
59
|
+
if not os.path.exists(manage_py_path):
|
|
60
|
+
UIFormatter.print_error("Could not find manage.py file in project root")
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
# Create app in the correct location
|
|
64
|
+
with change_cwd(apps_base_dir):
|
|
65
|
+
subprocess.run(["django-admin", "startapp", self.app_name], capture_output=True, text=True, check=True)
|
|
66
|
+
|
|
67
|
+
UIFormatter.print_success(f"Created Django app '{self.app_name}' in {apps_base_dir}")
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
def _add_to_installed_apps(self) -> bool:
|
|
71
|
+
settings_path = self._find_settings_path()
|
|
72
|
+
if not settings_path:
|
|
73
|
+
UIFormatter.print_error("Could not find Django settings directory")
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
base_settings_path = os.path.join(settings_path, "base.py")
|
|
77
|
+
if not os.path.exists(base_settings_path):
|
|
78
|
+
UIFormatter.print_error("Could not find base.py settings file")
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
with open(base_settings_path) as f:
|
|
82
|
+
content = f.read()
|
|
83
|
+
|
|
84
|
+
# Detect project structure to determine the correct app module path
|
|
85
|
+
is_nested, nested_dir, apps_base_dir = self._detect_project_structure()
|
|
86
|
+
|
|
87
|
+
# Determine the correct module path for the app
|
|
88
|
+
if is_nested and nested_dir:
|
|
89
|
+
app_module_path = f"{nested_dir}.{self.app_name}"
|
|
90
|
+
else:
|
|
91
|
+
app_module_path = self.app_name
|
|
92
|
+
|
|
93
|
+
# Check if app is already in USER_DEFINED_APPS section specifically
|
|
94
|
+
lines = content.split("\n")
|
|
95
|
+
in_user_apps = False
|
|
96
|
+
app_already_exists = False
|
|
97
|
+
|
|
98
|
+
for line in lines:
|
|
99
|
+
if "USER_DEFINED_APPS" in line and "=" in line:
|
|
100
|
+
in_user_apps = True
|
|
101
|
+
elif in_user_apps and line.strip() == "]":
|
|
102
|
+
in_user_apps = False
|
|
103
|
+
elif in_user_apps and (f'"{app_module_path}"' in line or f"'{app_module_path}'" in line):
|
|
104
|
+
app_already_exists = True
|
|
105
|
+
break
|
|
106
|
+
|
|
107
|
+
if app_already_exists:
|
|
108
|
+
UIFormatter.print_success(f"App '{app_module_path}' already configured in USER_DEFINED_APPS")
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
if "USER_DEFINED_APPS" not in content:
|
|
112
|
+
UIFormatter.print_error("Could not find USER_DEFINED_APPS section in base.py")
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
lines = content.split("\n")
|
|
116
|
+
new_lines = []
|
|
117
|
+
in_user_apps = False
|
|
118
|
+
apps_added = False
|
|
119
|
+
|
|
120
|
+
for line in lines:
|
|
121
|
+
if "USER_DEFINED_APPS" in line and "=" in line:
|
|
122
|
+
in_user_apps = True
|
|
123
|
+
new_lines.append(line)
|
|
124
|
+
elif in_user_apps and line.strip() == "]":
|
|
125
|
+
new_lines.append(f' "{app_module_path}",')
|
|
126
|
+
new_lines.append(line)
|
|
127
|
+
in_user_apps = False
|
|
128
|
+
apps_added = True
|
|
129
|
+
else:
|
|
130
|
+
new_lines.append(line)
|
|
131
|
+
|
|
132
|
+
if not apps_added:
|
|
133
|
+
UIFormatter.print_error("Could not find USER_DEFINED_APPS section in base.py")
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
with open(base_settings_path, "w") as f:
|
|
137
|
+
f.write("\n".join(new_lines))
|
|
138
|
+
|
|
139
|
+
UIFormatter.print_success(f"Added '{app_module_path}' to USER_DEFINED_APPS in base.py")
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
def _find_settings_path(self) -> Optional[str]:
|
|
143
|
+
# Look for project directories that contain settings
|
|
144
|
+
for item in os.listdir(self.current_dir):
|
|
145
|
+
if os.path.isdir(item) and not item.startswith(".") and item != "__pycache__":
|
|
146
|
+
# Check if this directory contains Django project files
|
|
147
|
+
project_path = os.path.join(self.current_dir, item)
|
|
148
|
+
settings_path = os.path.join(project_path, "settings")
|
|
149
|
+
|
|
150
|
+
# Check if settings directory exists with base.py
|
|
151
|
+
if os.path.exists(settings_path) and os.path.exists(os.path.join(settings_path, "base.py")):
|
|
152
|
+
return settings_path
|
|
153
|
+
|
|
154
|
+
# Fallback: look for settings in current directory
|
|
155
|
+
settings_path = os.path.join(self.current_dir, "settings")
|
|
156
|
+
if os.path.exists(settings_path) and os.path.exists(os.path.join(settings_path, "base.py")):
|
|
157
|
+
return settings_path
|
|
158
|
+
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
def _detect_project_structure(self) -> tuple[bool, Optional[str], str]:
|
|
162
|
+
# Step 1: find project directory containing settings/base.py
|
|
163
|
+
project_dir: Optional[str] = None
|
|
164
|
+
settings_base_path: Optional[str] = None
|
|
165
|
+
for item in os.listdir(self.current_dir):
|
|
166
|
+
if os.path.isdir(item) and not item.startswith(".") and item != "__pycache__":
|
|
167
|
+
candidate = os.path.join(self.current_dir, item)
|
|
168
|
+
base_py = os.path.join(candidate, "settings", "base.py")
|
|
169
|
+
if os.path.exists(base_py):
|
|
170
|
+
project_dir = candidate
|
|
171
|
+
settings_base_path = base_py
|
|
172
|
+
break
|
|
173
|
+
|
|
174
|
+
# Fallback: current_dir/settings/base.py
|
|
175
|
+
if project_dir is None:
|
|
176
|
+
base_py = os.path.join(self.current_dir, "settings", "base.py")
|
|
177
|
+
if os.path.exists(base_py):
|
|
178
|
+
project_dir = self.current_dir
|
|
179
|
+
settings_base_path = base_py
|
|
180
|
+
|
|
181
|
+
if project_dir is None:
|
|
182
|
+
# Not a Django project structure
|
|
183
|
+
return False, None, self.current_dir
|
|
184
|
+
|
|
185
|
+
# Step 2: try to infer nested_dir from USER_DEFINED_APPS in base.py
|
|
186
|
+
try:
|
|
187
|
+
if settings_base_path and os.path.exists(settings_base_path):
|
|
188
|
+
with open(settings_base_path) as f:
|
|
189
|
+
base_content = f.read()
|
|
190
|
+
# Find lines inside USER_DEFINED_APPS and extract first dotted app
|
|
191
|
+
in_user_apps = False
|
|
192
|
+
for line in base_content.splitlines():
|
|
193
|
+
if "USER_DEFINED_APPS" in line and "=" in line:
|
|
194
|
+
in_user_apps = True
|
|
195
|
+
continue
|
|
196
|
+
if in_user_apps and "]" in line:
|
|
197
|
+
break
|
|
198
|
+
if in_user_apps:
|
|
199
|
+
line_stripped = line.strip().strip(",")
|
|
200
|
+
if line_stripped.startswith('"') or line_stripped.startswith("'"):
|
|
201
|
+
app_label = line_stripped.strip("\"'")
|
|
202
|
+
if "." in app_label:
|
|
203
|
+
prefix = app_label.split(".", 1)[0]
|
|
204
|
+
nested_path = os.path.join(self.current_dir, prefix)
|
|
205
|
+
if os.path.isdir(nested_path):
|
|
206
|
+
return True, prefix, nested_path
|
|
207
|
+
except Exception:
|
|
208
|
+
# Ignore parsing issues and continue with heuristics
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
# Step 3: look for existing package directories in project root
|
|
212
|
+
preferred = ["apps", "modules", "packages"]
|
|
213
|
+
for name in preferred:
|
|
214
|
+
nested_path = os.path.join(self.current_dir, name)
|
|
215
|
+
if os.path.isdir(nested_path) and os.path.exists(os.path.join(nested_path, "__init__.py")):
|
|
216
|
+
return True, name, nested_path
|
|
217
|
+
|
|
218
|
+
# Any other directory with __init__.py qualifies as a package
|
|
219
|
+
for item in os.listdir(self.current_dir):
|
|
220
|
+
if item in ("__pycache__",) or item.startswith("."):
|
|
221
|
+
continue
|
|
222
|
+
nested_path = os.path.join(self.current_dir, item)
|
|
223
|
+
if os.path.isdir(nested_path) and os.path.exists(os.path.join(nested_path, "__init__.py")):
|
|
224
|
+
return True, item, nested_path
|
|
225
|
+
|
|
226
|
+
# Step 4: fallback
|
|
227
|
+
return False, None, self.current_dir
|
|
228
|
+
|
|
229
|
+
def _create_additional_files(self) -> bool:
|
|
230
|
+
"""Create additional files for the app (serializers.py, routes.py)."""
|
|
231
|
+
# Detect project structure to get correct app path
|
|
232
|
+
is_nested, nested_dir, apps_base_dir = self._detect_project_structure()
|
|
233
|
+
app_path = os.path.join(apps_base_dir, self.app_name)
|
|
234
|
+
|
|
235
|
+
# Create serializers.py
|
|
236
|
+
context = {"app_name": self.app_name}
|
|
237
|
+
serializers_content = template_engine.render_template("serializers.j2", context)
|
|
238
|
+
create_file_with_content(
|
|
239
|
+
os.path.join(app_path, "serializers.py"),
|
|
240
|
+
serializers_content,
|
|
241
|
+
f"Created {self.app_name}/serializers.py",
|
|
242
|
+
should_format=True,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Create routes.py
|
|
246
|
+
routes_content = template_engine.render_template("routes.j2", context)
|
|
247
|
+
create_file_with_content(
|
|
248
|
+
os.path.join(app_path, "routes.py"),
|
|
249
|
+
routes_content,
|
|
250
|
+
f"Created {self.app_name}/routes.py",
|
|
251
|
+
should_format=True,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
return True
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CI/CD pipeline manager for djinit
|
|
3
|
+
Handles creation of Github Actions and GitLab CI onfigurations
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
from djinit.scripts.template_engine import template_engine
|
|
9
|
+
from djinit.utils import change_cwd, create_file_with_content
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CICDConfigGenerator:
|
|
13
|
+
def __init__(self, project_root: str, project_name: str):
|
|
14
|
+
self.project_root = project_root
|
|
15
|
+
self.project_name = project_name
|
|
16
|
+
|
|
17
|
+
def create_github_actions(self) -> bool:
|
|
18
|
+
with change_cwd(self.project_root):
|
|
19
|
+
github_dir = os.path.join(self.project_root, ".github", "workflows")
|
|
20
|
+
os.makedirs(github_dir, exist_ok=True)
|
|
21
|
+
|
|
22
|
+
context = {"project_name": self.project_name}
|
|
23
|
+
workflow_content = template_engine.render_template("github_actions_ci.j2", context)
|
|
24
|
+
workflow_file = os.path.join(github_dir, "ci.yml")
|
|
25
|
+
create_file_with_content(
|
|
26
|
+
workflow_file,
|
|
27
|
+
workflow_content,
|
|
28
|
+
"Created Github Actions workflow (ci.yml)",
|
|
29
|
+
)
|
|
30
|
+
return True
|
|
31
|
+
|
|
32
|
+
def create_gitlab_ci(self) -> bool:
|
|
33
|
+
with change_cwd(self.project_root):
|
|
34
|
+
context = {"project_name": self.project_name}
|
|
35
|
+
gitlab_ci_content = template_engine.render_template("gitlab_ci.j2", context)
|
|
36
|
+
create_file_with_content(
|
|
37
|
+
".gitlab-ci.yml",
|
|
38
|
+
gitlab_ci_content,
|
|
39
|
+
"Created GitLab CI configuration (.gitlab-ci.yml)",
|
|
40
|
+
)
|
|
41
|
+
return True
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command handlers for Django project setup CLI.
|
|
3
|
+
Handles different commands like secret key generation and app creation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from djinit.scripts.console_ui import UIColors, UIFormatter, console
|
|
10
|
+
from djinit.scripts.name_validator import validate_app_name
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_arguments() -> argparse.Namespace:
|
|
14
|
+
parser = argparse.ArgumentParser(description="Django project setup tool", prog="django-init")
|
|
15
|
+
|
|
16
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
17
|
+
|
|
18
|
+
# Setup command (default)
|
|
19
|
+
setup_parser = subparsers.add_parser("setup", help="Create a new Django project")
|
|
20
|
+
setup_parser.add_argument("--project", help="Django project name")
|
|
21
|
+
setup_parser.add_argument("--app", help="Django app name")
|
|
22
|
+
|
|
23
|
+
# Secret command
|
|
24
|
+
secret_parser = subparsers.add_parser("secret", help="Generate Django secret keys")
|
|
25
|
+
secret_parser.add_argument("--count", type=int, default=3, help="Number of keys to generate")
|
|
26
|
+
secret_parser.add_argument("--length", type=int, default=50, help="Length of each secret key")
|
|
27
|
+
|
|
28
|
+
# App command
|
|
29
|
+
app_parser = subparsers.add_parser("app", help="Create one or more Django apps (comma or space separated)")
|
|
30
|
+
app_parser.add_argument(
|
|
31
|
+
"app_name",
|
|
32
|
+
nargs="+",
|
|
33
|
+
help=(
|
|
34
|
+
"App name(s) or comma-separated list. Examples: \n"
|
|
35
|
+
" djinit app users \n"
|
|
36
|
+
" djinit app users,products,orders \n"
|
|
37
|
+
" djinit app users products orders \n"
|
|
38
|
+
" djinit app users, products, orders"
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return parser.parse_args()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def handle_secret_command(args: argparse.Namespace) -> None:
|
|
46
|
+
from djinit.scripts.secretkey_generator import display_secret_keys, generate_multiple_keys
|
|
47
|
+
|
|
48
|
+
# Generate keys
|
|
49
|
+
keys = generate_multiple_keys(args.count, args.length)
|
|
50
|
+
|
|
51
|
+
# Display results
|
|
52
|
+
UIFormatter.print_info("")
|
|
53
|
+
display_secret_keys(keys)
|
|
54
|
+
|
|
55
|
+
# Show usage instructions
|
|
56
|
+
from rich.panel import Panel
|
|
57
|
+
from rich.text import Text
|
|
58
|
+
|
|
59
|
+
instructions = Text()
|
|
60
|
+
instructions.append("📋 Usage Instructions:\n", style=UIColors.ACCENT)
|
|
61
|
+
instructions.append("1. Copy the appropriate secret key for your environment\n")
|
|
62
|
+
instructions.append("2. Add it to your .env file:\n")
|
|
63
|
+
instructions.append(" SECRET_KEY=your_secret_key_here\n", style=UIColors.CODE)
|
|
64
|
+
instructions.append("3. Or set it as an environment variable:\n")
|
|
65
|
+
instructions.append(" export SECRET_KEY=your_secret_key_here\n", style=UIColors.CODE)
|
|
66
|
+
instructions.append("4. Never commit secret keys to version control!\n", style=UIColors.WARNING)
|
|
67
|
+
|
|
68
|
+
console.print(Panel(instructions, title="💡 How to Use", border_style="blue"))
|
|
69
|
+
console.print()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def handle_app_command(args: argparse.Namespace) -> None:
|
|
73
|
+
from djinit.scripts.app_generator import AppManager
|
|
74
|
+
|
|
75
|
+
# Display welcome message
|
|
76
|
+
UIFormatter.print_info("")
|
|
77
|
+
UIFormatter.print_header("Django App Creation")
|
|
78
|
+
UIFormatter.print_info("")
|
|
79
|
+
|
|
80
|
+
# Parse comma-separated names and validate
|
|
81
|
+
# args.app_name is a list due to nargs="+"; split each token by comma
|
|
82
|
+
tokens = args.app_name if isinstance(args.app_name, list) else [args.app_name]
|
|
83
|
+
pieces = []
|
|
84
|
+
for token in tokens:
|
|
85
|
+
if token is None:
|
|
86
|
+
continue
|
|
87
|
+
for part in str(token).split(","):
|
|
88
|
+
part = part.strip()
|
|
89
|
+
if part:
|
|
90
|
+
pieces.append(part)
|
|
91
|
+
app_names = pieces
|
|
92
|
+
|
|
93
|
+
# If no comma, keep single name behavior
|
|
94
|
+
if not app_names:
|
|
95
|
+
UIFormatter.print_error("App name cannot be empty")
|
|
96
|
+
sys.exit(1)
|
|
97
|
+
|
|
98
|
+
# Deduplicate while preserving order
|
|
99
|
+
seen = set()
|
|
100
|
+
deduped_names = []
|
|
101
|
+
for name in app_names:
|
|
102
|
+
if name not in seen:
|
|
103
|
+
deduped_names.append(name)
|
|
104
|
+
seen.add(name)
|
|
105
|
+
|
|
106
|
+
# Validate all names first
|
|
107
|
+
for name in deduped_names:
|
|
108
|
+
is_valid, error_msg = validate_app_name(name)
|
|
109
|
+
if not is_valid:
|
|
110
|
+
UIFormatter.print_error(f"Invalid app name '{name}': {error_msg}")
|
|
111
|
+
sys.exit(1)
|
|
112
|
+
|
|
113
|
+
# Create apps sequentially
|
|
114
|
+
any_failure = False
|
|
115
|
+
for name in deduped_names:
|
|
116
|
+
app_manager = AppManager(name)
|
|
117
|
+
success = app_manager.create_app()
|
|
118
|
+
if not success:
|
|
119
|
+
UIFormatter.print_error(f"Failed to create Django app '{name}'")
|
|
120
|
+
any_failure = True
|
|
121
|
+
break
|
|
122
|
+
UIFormatter.print_success(f"Django app '{name}' created successfully!")
|
|
123
|
+
|
|
124
|
+
if any_failure:
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
|
|
127
|
+
# Show next steps
|
|
128
|
+
from rich.panel import Panel
|
|
129
|
+
from rich.text import Text
|
|
130
|
+
|
|
131
|
+
instructions = Text()
|
|
132
|
+
instructions.append("🚀 Next Steps:\n", style=UIColors.ACCENT)
|
|
133
|
+
instructions.append("1. The app(s) have been added to INSTALLED_APPS in settings/base.py\n")
|
|
134
|
+
instructions.append("2. Create your models in each app's models.py\n")
|
|
135
|
+
instructions.append("3. Run migrations: python manage.py makemigrations\n")
|
|
136
|
+
instructions.append("4. Apply migrations: python manage.py migrate\n")
|
|
137
|
+
instructions.append("5. Create views, serializers and routes(URLs) for your app(s)\n")
|
|
138
|
+
|
|
139
|
+
console.print(Panel(instructions, title="💡 What's Next", border_style="green"))
|
|
140
|
+
UIFormatter.print_info("")
|