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.
Files changed (43) hide show
  1. djinit/__init__.py +0 -0
  2. djinit/cli.py +109 -0
  3. djinit/generator.py +74 -0
  4. djinit/scripts/__init__.py +0 -0
  5. djinit/scripts/app_generator.py +254 -0
  6. djinit/scripts/cicd_config_generator.py +41 -0
  7. djinit/scripts/command_handler.py +140 -0
  8. djinit/scripts/console_ui.py +381 -0
  9. djinit/scripts/deployment_files_generator.py +118 -0
  10. djinit/scripts/files.py +163 -0
  11. djinit/scripts/input_handler.py +406 -0
  12. djinit/scripts/name_validator.py +52 -0
  13. djinit/scripts/project.py +111 -0
  14. djinit/scripts/secretkey_generator.py +59 -0
  15. djinit/scripts/settings.py +86 -0
  16. djinit/scripts/template_engine.py +80 -0
  17. djinit/templates/app_urls.j2 +9 -0
  18. djinit/templates/asgi.j2 +9 -0
  19. djinit/templates/env_sample.j2 +23 -0
  20. djinit/templates/github_actions_ci.j2 +109 -0
  21. djinit/templates/gitignore.j2 +89 -0
  22. djinit/templates/gitlab_ci.j2 +133 -0
  23. djinit/templates/justfile.j2 +154 -0
  24. djinit/templates/manage_py.j2 +25 -0
  25. djinit/templates/procfile.j2 +20 -0
  26. djinit/templates/project_urls.j2 +29 -0
  27. djinit/templates/pyproject_toml.j2 +41 -0
  28. djinit/templates/readme.j2 +60 -0
  29. djinit/templates/requirements.j2 +11 -0
  30. djinit/templates/routes.j2 +16 -0
  31. djinit/templates/runtime_txt.j2 +1 -0
  32. djinit/templates/serializers.j2 +13 -0
  33. djinit/templates/settings_base.j2 +161 -0
  34. djinit/templates/settings_development.j2 +27 -0
  35. djinit/templates/settings_production.j2 +51 -0
  36. djinit/templates/wsgi.j2 +9 -0
  37. djinit/utils.py +52 -0
  38. djinitx-0.1.0.dist-info/METADATA +182 -0
  39. djinitx-0.1.0.dist-info/RECORD +43 -0
  40. djinitx-0.1.0.dist-info/WHEEL +5 -0
  41. djinitx-0.1.0.dist-info/entry_points.txt +3 -0
  42. djinitx-0.1.0.dist-info/licenses/LICENSE +21 -0
  43. 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("")