djinitx 0.3.0__tar.gz → 0.3.2__tar.gz
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.
- {djinitx-0.3.0 → djinitx-0.3.2}/.github/workflows/publish.yml +2 -2
- {djinitx-0.3.0 → djinitx-0.3.2}/PKG-INFO +3 -5
- {djinitx-0.3.0 → djinitx-0.3.2}/TODO.md +2 -1
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/cli.py +8 -8
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/commands/handler.py +7 -17
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/services/app.py +38 -14
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/services/files.py +98 -66
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/services/project.py +22 -19
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/ui/console.py +19 -10
- djinitx-0.3.2/djinit/ui/input.py +337 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/utils/common.py +22 -0
- djinitx-0.3.0/djinit/utils/security.py → djinitx-0.3.2/djinit/utils/secretkey.py +1 -21
- {djinitx-0.3.0 → djinitx-0.3.2}/pyproject.toml +9 -5
- djinitx-0.3.2/requirements.txt +7 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/setup.py +2 -1
- {djinitx-0.3.0 → djinitx-0.3.2}/uv.lock +118 -27
- djinitx-0.3.0/djinit/ui/input.py +0 -597
- djinitx-0.3.0/requirements.txt +0 -4
- {djinitx-0.3.0 → djinitx-0.3.2}/.github/dependabot.yml +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/.github/workflows/ci.yml +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/.github/workflows/codeql.yml +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/.github/workflows/dependabot-auto-merge.yml +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/.github/workflows/pre-commit.yml +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/.gitignore +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/.pre-commit-config.yaml +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/.python-version +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/LICENSE +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/MANIFEST.in +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/README.md +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/__init__.py +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/commands/__init__.py +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/core/__init__.py +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/core/config.py +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/core/types.py +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/generator.py +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/services/__init__.py +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/services/templates.py +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/components/admin.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/components/api_readme.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/components/apps.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/components/migrations_init.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/components/models.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/components/models_readme.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/components/routes.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/components/serializers.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/components/tests.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/components/urls.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/components/views.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/config/asgi.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/config/settings/base.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/config/settings/development.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/config/settings/production.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/config/urls/base.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/config/urls/with_api.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/config/urls/with_apps.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/config/wsgi.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/api/urls.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/api/v1/urls.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/apps/generic/models.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/apps/generic/serializers.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/apps/generic/services.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/apps/generic/tests.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/apps/generic/urls.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/apps/generic/views.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/apps/users/models.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/apps/users/serializers.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/apps/users/services.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/apps/users/tests.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/apps/users/urls.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/apps/users/views.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/core/exceptions.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/core/middleware/request_logger.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/core/mixins/timestamped_model.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/predefined/core/utils/responses.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/unified/apps/core/apps.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/unified/apps/core/models/__init__.py.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/unified/apps/core/models/base.py.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/unified/apps/core/models/mixins.py.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/unified/apps/core/utils/__init__.py.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/presets/unified/apps/core/utils/responses.py.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/project/ci/github_actions.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/project/ci/gitlab_ci.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/project/env_sample.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/project/gitignore.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/project/init.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/project/justfile.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/project/manage_py.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/project/procfile.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/project/pyproject_toml.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/project/readme.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/project/requirements.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/templates/project/runtime_txt.j2 +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/ui/__init__.py +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/utils/__init__.py +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/utils/django.py +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/utils/exceptions.py +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/djinit/utils/validators.py +0 -0
- {djinitx-0.3.0 → djinitx-0.3.2}/ruff.toml +0 -0
|
@@ -11,9 +11,9 @@ jobs:
|
|
|
11
11
|
permissions:
|
|
12
12
|
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
|
13
13
|
steps:
|
|
14
|
-
- uses: actions/checkout@
|
|
14
|
+
- uses: actions/checkout@v6
|
|
15
15
|
- name: Set up Python
|
|
16
|
-
uses: actions/setup-python@
|
|
16
|
+
uses: actions/setup-python@v6
|
|
17
17
|
with:
|
|
18
18
|
python-version: '3.12'
|
|
19
19
|
cache: 'pip'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: djinitx
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: A CLI tool to setup Django project
|
|
5
5
|
Project-URL: Homepage, https://github.com/S4NKALP/djinit
|
|
6
6
|
Author-email: Sankalp Tharu <sankalptharu50028@gmail.com>
|
|
@@ -25,12 +25,10 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
25
25
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
26
26
|
Classifier: Topic :: Software Development :: Code Generators
|
|
27
27
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
-
Requires-Python: >=3.
|
|
29
|
-
Requires-Dist: click>=8.3.0
|
|
28
|
+
Requires-Python: >=3.10
|
|
30
29
|
Requires-Dist: jinja2>=3.1.0
|
|
31
|
-
Requires-Dist:
|
|
30
|
+
Requires-Dist: questionary>=2.0.1
|
|
32
31
|
Requires-Dist: rich>=14.2.0
|
|
33
|
-
Requires-Dist: ruff>=0.14.2
|
|
34
32
|
Description-Content-Type: text/markdown
|
|
35
33
|
|
|
36
34
|
# djinit
|
|
@@ -9,7 +9,6 @@ from typing import Callable, List, Tuple
|
|
|
9
9
|
from djinit.services.files import FileManager
|
|
10
10
|
from djinit.services.project import ProjectManager
|
|
11
11
|
from djinit.ui.console import UIFormatter
|
|
12
|
-
from djinit.utils.security import generate_secret_command
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class Cli:
|
|
@@ -27,7 +26,8 @@ class Cli:
|
|
|
27
26
|
self.project_manager = ProjectManager(project_dir, project_name, app_names, metadata)
|
|
28
27
|
self.file_manager = FileManager(self.project_root, project_name, app_names, metadata)
|
|
29
28
|
|
|
30
|
-
def
|
|
29
|
+
def _normalize_metadata(self) -> None:
|
|
30
|
+
"""Normalize metadata and app names based on structure type."""
|
|
31
31
|
if self.metadata.get("predefined_structure"):
|
|
32
32
|
self.metadata.setdefault("project_module_name", "config")
|
|
33
33
|
self.metadata.setdefault("nested_apps", True)
|
|
@@ -36,7 +36,7 @@ class Cli:
|
|
|
36
36
|
self.app_names = ["users", "core"]
|
|
37
37
|
self.project_manager.app_names = self.app_names
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
elif self.metadata.get("unified_structure"):
|
|
40
40
|
self.metadata.setdefault("project_module_name", "core")
|
|
41
41
|
self.metadata.setdefault("nested_apps", True)
|
|
42
42
|
self.metadata.setdefault("nested_dir", "apps")
|
|
@@ -44,12 +44,15 @@ class Cli:
|
|
|
44
44
|
self.app_names = []
|
|
45
45
|
self.project_manager.app_names = self.app_names
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
elif self.metadata.get("single_structure"):
|
|
48
48
|
self.metadata.setdefault("nested_apps", False)
|
|
49
49
|
if not self.app_names:
|
|
50
50
|
self.app_names = []
|
|
51
51
|
self.project_manager.app_names = self.app_names
|
|
52
52
|
|
|
53
|
+
def run_setup(self) -> bool:
|
|
54
|
+
self._normalize_metadata()
|
|
55
|
+
|
|
53
56
|
steps: List[Tuple[str, Callable[[], None]]] = []
|
|
54
57
|
|
|
55
58
|
steps.append(("Creating Django project", self.project_manager.create_project))
|
|
@@ -100,6 +103,7 @@ class Cli:
|
|
|
100
103
|
self.file_manager.create_requirements,
|
|
101
104
|
self.file_manager.create_readme,
|
|
102
105
|
self.file_manager.create_env_file,
|
|
106
|
+
self.file_manager.create_djinit_config,
|
|
103
107
|
lambda: self.file_manager.create_pyproject_toml(self.metadata),
|
|
104
108
|
]
|
|
105
109
|
|
|
@@ -114,7 +118,3 @@ class Cli:
|
|
|
114
118
|
|
|
115
119
|
if self.metadata.get("use_gitlab_ci", True):
|
|
116
120
|
self.file_manager.create_gitlab_ci()
|
|
117
|
-
|
|
118
|
-
def generate_secret_keys(self) -> bool:
|
|
119
|
-
generate_secret_command()
|
|
120
|
-
return True
|
|
@@ -11,7 +11,7 @@ from rich.text import Text
|
|
|
11
11
|
|
|
12
12
|
from djinit.services.app import AppManager
|
|
13
13
|
from djinit.ui.console import UIColors, UIFormatter, console
|
|
14
|
-
from djinit.utils.
|
|
14
|
+
from djinit.utils.secretkey import display_secret_keys, generate_multiple_keys
|
|
15
15
|
from djinit.utils.validators import validate_app_name
|
|
16
16
|
|
|
17
17
|
|
|
@@ -67,27 +67,17 @@ def handle_app_command(args: argparse.Namespace) -> None:
|
|
|
67
67
|
UIFormatter.print_header("Django App Creation")
|
|
68
68
|
UIFormatter.print_info("")
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
continue
|
|
75
|
-
for part in str(token).split(","):
|
|
76
|
-
part = part.strip()
|
|
77
|
-
if part:
|
|
78
|
-
pieces.append(part)
|
|
79
|
-
app_names = pieces
|
|
70
|
+
raw_tokens = args.app_name if isinstance(args.app_name, list) else [args.app_name]
|
|
71
|
+
|
|
72
|
+
# Flatten and clean tokens
|
|
73
|
+
app_names = [part.strip() for token in raw_tokens if token for part in str(token).split(",") if part.strip()]
|
|
80
74
|
|
|
81
75
|
if not app_names:
|
|
82
76
|
UIFormatter.print_error("App name cannot be empty")
|
|
83
77
|
sys.exit(1)
|
|
84
78
|
|
|
85
|
-
|
|
86
|
-
deduped_names =
|
|
87
|
-
for name in app_names:
|
|
88
|
-
if name not in seen:
|
|
89
|
-
deduped_names.append(name)
|
|
90
|
-
seen.add(name)
|
|
79
|
+
# Deduplicate while preserving order
|
|
80
|
+
deduped_names = list(dict.fromkeys(app_names))
|
|
91
81
|
|
|
92
82
|
for name in deduped_names:
|
|
93
83
|
is_valid, error_msg = validate_app_name(name)
|
|
@@ -16,6 +16,7 @@ from djinit.utils.common import (
|
|
|
16
16
|
extract_existing_apps,
|
|
17
17
|
find_project_dir,
|
|
18
18
|
find_settings_path,
|
|
19
|
+
get_djinit_config,
|
|
19
20
|
insert_apps_into_user_defined_apps,
|
|
20
21
|
is_django_project,
|
|
21
22
|
)
|
|
@@ -27,6 +28,7 @@ class AppManager:
|
|
|
27
28
|
self.app_name = app_name
|
|
28
29
|
self.current_dir = os.getcwd()
|
|
29
30
|
self.manage_py_path = os.path.join(self.current_dir, "manage.py")
|
|
31
|
+
self.config = get_djinit_config(self.current_dir)
|
|
30
32
|
self._project_structure_cache = None
|
|
31
33
|
|
|
32
34
|
def create_app(self) -> bool:
|
|
@@ -126,36 +128,58 @@ class AppManager:
|
|
|
126
128
|
def _get_project_structure(self) -> tuple[bool, Optional[str], str]:
|
|
127
129
|
"""Get project structure with caching to avoid repeated detection."""
|
|
128
130
|
if self._project_structure_cache is None:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
if self.config:
|
|
132
|
+
apps_config = self.config.get("apps", {})
|
|
133
|
+
nested = apps_config.get("nested", False)
|
|
134
|
+
nested_dir = apps_config.get("nested_dir")
|
|
135
|
+
|
|
136
|
+
# Determine base dir for apps
|
|
137
|
+
if nested and nested_dir:
|
|
138
|
+
apps_base_dir = os.path.join(self.current_dir, nested_dir)
|
|
139
|
+
else:
|
|
140
|
+
apps_base_dir = self.current_dir
|
|
141
|
+
|
|
142
|
+
self._project_structure_cache = (nested, nested_dir, apps_base_dir)
|
|
133
143
|
else:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
144
|
+
# Fallback to old detection method
|
|
145
|
+
project_dir, settings_base_path = find_project_dir(self.current_dir)
|
|
146
|
+
|
|
147
|
+
if project_dir is None:
|
|
148
|
+
self._project_structure_cache = (False, None, self.current_dir)
|
|
149
|
+
else:
|
|
150
|
+
self._project_structure_cache = detect_nested_structure_from_settings(
|
|
151
|
+
settings_base_path, self.current_dir
|
|
152
|
+
)
|
|
137
153
|
return self._project_structure_cache
|
|
138
154
|
|
|
139
155
|
def _is_predefined_structure(self) -> bool:
|
|
156
|
+
if self.config:
|
|
157
|
+
return self.config.get("structure", {}).get("predefined", False)
|
|
158
|
+
|
|
140
159
|
apps_dir = os.path.join(self.current_dir, "apps")
|
|
141
160
|
api_dir = os.path.join(self.current_dir, "api")
|
|
142
161
|
return os.path.isdir(apps_dir) and os.path.isdir(api_dir)
|
|
143
162
|
|
|
144
163
|
def _is_restricted_structure(self) -> bool:
|
|
145
164
|
"""Check if the project structure is Unified or Single Folder."""
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
if self.config:
|
|
166
|
+
structure = self.config.get("structure", {})
|
|
167
|
+
return structure.get("unified", False) or structure.get("single", False)
|
|
168
|
+
|
|
169
|
+
# Check for Unified structure (apps/api exists)
|
|
170
|
+
if os.path.isdir(os.path.join(self.current_dir, "apps", "api")):
|
|
148
171
|
return True
|
|
149
172
|
|
|
150
|
-
|
|
151
|
-
if os.path.isdir(
|
|
173
|
+
# Check for Predefined structure (apps/ exists but not apps/api)
|
|
174
|
+
if os.path.isdir(os.path.join(self.current_dir, "apps")):
|
|
152
175
|
return False
|
|
153
176
|
|
|
177
|
+
# Check for Single Folder structure (models/ and api/ exist in project dir)
|
|
154
178
|
project_dir, _ = find_project_dir(self.current_dir)
|
|
155
179
|
if project_dir:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if
|
|
180
|
+
has_models = os.path.isdir(os.path.join(project_dir, "models"))
|
|
181
|
+
has_api = os.path.isdir(os.path.join(project_dir, "api"))
|
|
182
|
+
if has_models and has_api:
|
|
159
183
|
return True
|
|
160
184
|
|
|
161
185
|
return False
|
|
@@ -45,10 +45,12 @@ class FileManager:
|
|
|
45
45
|
def _create_files_from_specs(self, base_dir: str, folder_specs: dict, base_context: dict = None) -> None:
|
|
46
46
|
"""Helper to create multiple folders and files from a specification dict."""
|
|
47
47
|
base_context = base_context or {}
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
|
|
49
|
+
for folder_name, file_templates in folder_specs.items():
|
|
50
|
+
folder_path = os.path.join(base_dir, folder_name)
|
|
50
51
|
create_directory_with_init(folder_path, f"Created {folder_path}/__init__.py")
|
|
51
|
-
|
|
52
|
+
|
|
53
|
+
for filename, template_path in file_templates:
|
|
52
54
|
file_path = os.path.join(folder_path, filename)
|
|
53
55
|
create_file_from_template(file_path, template_path, base_context, f"Created {file_path}")
|
|
54
56
|
|
|
@@ -66,6 +68,52 @@ class FileManager:
|
|
|
66
68
|
message = f"Created {prefix}{subdir}/__init__.py" if prefix else f"Created {subdir}/__init__.py"
|
|
67
69
|
create_directory_with_init(subdir_path, message)
|
|
68
70
|
|
|
71
|
+
def _create_settings_package(self, settings_dir: str, base_context: dict, prefix: str) -> None:
|
|
72
|
+
"""Helper to create settings package files (base, development, production)."""
|
|
73
|
+
from djinit.utils.secretkey import generate_secret_key
|
|
74
|
+
|
|
75
|
+
secret_key = generate_secret_key()
|
|
76
|
+
dev_context = {"secret_key": secret_key}
|
|
77
|
+
|
|
78
|
+
for filename, context in [
|
|
79
|
+
("base.py", base_context),
|
|
80
|
+
("development.py", dev_context),
|
|
81
|
+
("production.py", base_context),
|
|
82
|
+
]:
|
|
83
|
+
filepath = os.path.join(settings_dir, filename)
|
|
84
|
+
template_path = f"config/settings/{filename.replace('.py', '')}.j2"
|
|
85
|
+
self._render_and_create_file(
|
|
86
|
+
filepath,
|
|
87
|
+
template_path,
|
|
88
|
+
context,
|
|
89
|
+
f"Created {prefix}/settings/{filename}",
|
|
90
|
+
base_dir=os.path.dirname(
|
|
91
|
+
settings_dir
|
|
92
|
+
), # Not strictly used by _render_and_create_file but good for context
|
|
93
|
+
should_format=True,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def _create_lifecycle_files(
|
|
97
|
+
self, target_dir: str, prefix: str, api_module: str = None, comment_out_api: bool = False
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Helper to create urls.py, wsgi.py, and asgi.py."""
|
|
100
|
+
|
|
101
|
+
urls_context = {"api_module": api_module}
|
|
102
|
+
if comment_out_api:
|
|
103
|
+
urls_context["comment_out_api_url"] = True
|
|
104
|
+
|
|
105
|
+
files = [
|
|
106
|
+
("urls.py", "config/urls/with_api.j2", urls_context),
|
|
107
|
+
("wsgi.py", "config/wsgi.j2", {}),
|
|
108
|
+
("asgi.py", "config/asgi.j2", {}),
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
for filename, template, context in files:
|
|
112
|
+
filepath = os.path.join(target_dir, filename)
|
|
113
|
+
self._render_and_create_file(
|
|
114
|
+
filepath, template, context, f"Created {prefix}/{filename}", should_format=True
|
|
115
|
+
)
|
|
116
|
+
|
|
69
117
|
def create_gitignore(self) -> None:
|
|
70
118
|
self._render_and_create_file(".gitignore", "project/gitignore.j2", {}, "Created .gitignore file")
|
|
71
119
|
|
|
@@ -208,43 +256,17 @@ class FileManager:
|
|
|
208
256
|
settings_dir = os.path.join(core_dir, "settings")
|
|
209
257
|
create_directory_with_init(settings_dir, "Created core/settings/__init__.py")
|
|
210
258
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
secret_key = generate_secret_key()
|
|
214
|
-
|
|
215
|
-
# In this new structure, 'apps' is the main app.
|
|
216
|
-
# We assume the user wants 'apps' to be the app label.
|
|
217
|
-
# Or perhaps 'apps' is just a container and the components inside are what matters?
|
|
218
|
-
# Given the structure: apps/models, apps/views etc.
|
|
219
|
-
# It seems 'apps' is acting as the app.
|
|
220
|
-
|
|
259
|
+
# Create settings files
|
|
221
260
|
base_context = {
|
|
222
|
-
"project_name": "core",
|
|
223
|
-
"app_names": ["apps"],
|
|
261
|
+
"project_name": "core",
|
|
262
|
+
"app_names": ["apps"],
|
|
224
263
|
"use_database_url": self.metadata.get("use_database_url", True),
|
|
225
264
|
"database_type": self.metadata.get("database_type", "postgresql"),
|
|
226
265
|
}
|
|
227
|
-
|
|
266
|
+
self._create_settings_package(settings_dir, base_context, "core")
|
|
228
267
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
("development.py", dev_context),
|
|
232
|
-
("production.py", base_context),
|
|
233
|
-
]:
|
|
234
|
-
filepath = os.path.join(settings_dir, filename)
|
|
235
|
-
template_path = f"config/settings/{filename.replace('.py', '')}.j2"
|
|
236
|
-
create_file_from_template(
|
|
237
|
-
filepath, template_path, context, f"Created core/settings/{filename}", should_format=True
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
core_files = [
|
|
241
|
-
("urls.py", "config/urls/with_api.j2", {"api_module": "apps.api"}),
|
|
242
|
-
("wsgi.py", "config/wsgi.j2", {}),
|
|
243
|
-
("asgi.py", "config/asgi.j2", {}),
|
|
244
|
-
]
|
|
245
|
-
for filename, template, context in core_files:
|
|
246
|
-
filepath = os.path.join(core_dir, filename)
|
|
247
|
-
create_file_from_template(filepath, template, context, f"Created core/{filename}", should_format=True)
|
|
268
|
+
# Create lifecycle files (urls, wsgi, asgi)
|
|
269
|
+
self._create_lifecycle_files(core_dir, "core", api_module="apps.api")
|
|
248
270
|
|
|
249
271
|
# 2. Create 'apps' directory (The Main App)
|
|
250
272
|
apps_dir = os.path.join(self.project_root, "apps")
|
|
@@ -287,43 +309,19 @@ class FileManager:
|
|
|
287
309
|
settings_dir = os.path.join(project_dir, "settings")
|
|
288
310
|
create_directory_with_init(settings_dir, f"Created {self.module_name}/settings/__init__.py")
|
|
289
311
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
secret_key = generate_secret_key()
|
|
293
|
-
|
|
312
|
+
# Create settings files
|
|
294
313
|
base_context = {
|
|
295
314
|
"project_name": self.module_name,
|
|
296
|
-
"app_names": [self.module_name],
|
|
315
|
+
"app_names": [self.module_name],
|
|
297
316
|
"use_database_url": self.metadata.get("use_database_url", True),
|
|
298
317
|
"database_type": self.metadata.get("database_type", "postgresql"),
|
|
299
318
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
for filename, context in [
|
|
303
|
-
("base.py", base_context),
|
|
304
|
-
("development.py", dev_context),
|
|
305
|
-
("production.py", base_context),
|
|
306
|
-
]:
|
|
307
|
-
filepath = os.path.join(settings_dir, filename)
|
|
308
|
-
template_path = f"config/settings/{filename.replace('.py', '')}.j2"
|
|
309
|
-
create_file_from_template(
|
|
310
|
-
filepath, template_path, context, f"Created {self.module_name}/settings/{filename}", should_format=True
|
|
311
|
-
)
|
|
319
|
+
self._create_settings_package(settings_dir, base_context, self.module_name)
|
|
312
320
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
{"api_module": f"{self.module_name}.api", "comment_out_api_url": True},
|
|
318
|
-
),
|
|
319
|
-
("wsgi.py", "config/wsgi.j2", {}),
|
|
320
|
-
("asgi.py", "config/asgi.j2", {}),
|
|
321
|
-
]
|
|
322
|
-
for filename, template, context in project_files:
|
|
323
|
-
filepath = os.path.join(project_dir, filename)
|
|
324
|
-
create_file_from_template(
|
|
325
|
-
filepath, template, context, f"Created {self.module_name}/{filename}", should_format=True
|
|
326
|
-
)
|
|
321
|
+
# Create lifecycle files (urls, wsgi, asgi)
|
|
322
|
+
self._create_lifecycle_files(
|
|
323
|
+
project_dir, self.module_name, api_module=f"{self.module_name}.api", comment_out_api=True
|
|
324
|
+
)
|
|
327
325
|
|
|
328
326
|
components = ["admin", "api", "models", "tests"]
|
|
329
327
|
self._create_subdirectories_with_init(project_dir, components, f"{self.module_name}/")
|
|
@@ -395,3 +393,37 @@ class FileManager:
|
|
|
395
393
|
context,
|
|
396
394
|
"Created GitLab CI configuration (.gitlab-ci.yml)",
|
|
397
395
|
)
|
|
396
|
+
|
|
397
|
+
def create_djinit_config(self) -> None:
|
|
398
|
+
"""Create .djinit configuration file."""
|
|
399
|
+
import json
|
|
400
|
+
|
|
401
|
+
is_predefined = self.metadata.get("predefined_structure", False)
|
|
402
|
+
is_unified = self.metadata.get("unified_structure", False)
|
|
403
|
+
is_single = self.metadata.get("single_structure", False)
|
|
404
|
+
is_standard = not (is_predefined or is_unified or is_single)
|
|
405
|
+
|
|
406
|
+
config = {
|
|
407
|
+
"project_name": self.project_name,
|
|
408
|
+
"structure": {
|
|
409
|
+
"standard": is_standard,
|
|
410
|
+
"predefined": is_predefined,
|
|
411
|
+
"unified": is_unified,
|
|
412
|
+
"single": is_single,
|
|
413
|
+
},
|
|
414
|
+
"apps": {
|
|
415
|
+
"nested": self.metadata.get("nested_apps", False),
|
|
416
|
+
"nested_dir": self.metadata.get("nested_dir"),
|
|
417
|
+
},
|
|
418
|
+
"settings": {
|
|
419
|
+
"use_database_url": self.metadata.get("use_database_url", True),
|
|
420
|
+
"database_type": self.metadata.get("database_type", "postgresql"),
|
|
421
|
+
},
|
|
422
|
+
"cicd": {
|
|
423
|
+
"github": self.metadata.get("use_github_actions", False),
|
|
424
|
+
"gitlab": self.metadata.get("use_gitlab_ci", False),
|
|
425
|
+
},
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
filepath = os.path.join(self.project_root, ".djinit")
|
|
429
|
+
create_file_with_content(filepath, json.dumps(config, indent=4), "Created .djinit configuration file")
|
|
@@ -89,23 +89,25 @@ class ProjectManager:
|
|
|
89
89
|
UIFormatter.print_success(f"Added apps to USER_DEFINED_APPS: {added_apps_str}")
|
|
90
90
|
|
|
91
91
|
def validate_project_structure(self) -> None:
|
|
92
|
+
join = lambda *args: os.path.join(self.project_root, *args)
|
|
93
|
+
|
|
92
94
|
required_files = [
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
join("manage.py"),
|
|
96
|
+
join(self.module_name, "__init__.py"),
|
|
97
|
+
join(self.module_name, "settings", "__init__.py"),
|
|
98
|
+
join(self.module_name, "settings", "base.py"),
|
|
99
|
+
join(self.module_name, "settings", "development.py"),
|
|
100
|
+
join(self.module_name, "settings", "production.py"),
|
|
101
|
+
join(self.module_name, "urls.py"),
|
|
102
|
+
join(self.module_name, "wsgi.py"),
|
|
103
|
+
join(self.module_name, "asgi.py"),
|
|
102
104
|
]
|
|
103
105
|
|
|
104
106
|
apps_base_dir = self._get_apps_base_dir()
|
|
105
107
|
|
|
106
108
|
if self.metadata.get("unified_structure"):
|
|
107
109
|
# Validate Unified Structure
|
|
108
|
-
apps_dir =
|
|
110
|
+
apps_dir = join("apps")
|
|
109
111
|
unified_files = [
|
|
110
112
|
os.path.join(apps_dir, "__init__.py"),
|
|
111
113
|
os.path.join(apps_dir, "apps.py"),
|
|
@@ -124,16 +126,17 @@ class ProjectManager:
|
|
|
124
126
|
|
|
125
127
|
elif not self.metadata.get("predefined_structure") and not self.metadata.get("single_structure"):
|
|
126
128
|
for app_name in self.app_names:
|
|
129
|
+
app_path = lambda f, app=app_name: os.path.join(apps_base_dir, app, f)
|
|
127
130
|
app_files = [
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
app_path("__init__.py"),
|
|
132
|
+
app_path("apps.py"),
|
|
133
|
+
app_path("models.py"),
|
|
134
|
+
app_path("views.py"),
|
|
135
|
+
app_path("serializers.py"),
|
|
136
|
+
app_path("routes.py"),
|
|
137
|
+
app_path("tests.py"),
|
|
138
|
+
app_path("migrations"),
|
|
139
|
+
app_path("admin.py"),
|
|
137
140
|
]
|
|
138
141
|
required_files.extend(app_files)
|
|
139
142
|
|
|
@@ -6,6 +6,7 @@ Provides clean, minimal styling and user interface components.
|
|
|
6
6
|
from contextlib import contextmanager
|
|
7
7
|
from typing import Any, Dict, List, Optional
|
|
8
8
|
|
|
9
|
+
import questionary
|
|
9
10
|
from rich import box
|
|
10
11
|
from rich.console import Console
|
|
11
12
|
from rich.panel import Panel
|
|
@@ -164,20 +165,28 @@ class UIFormatter:
|
|
|
164
165
|
|
|
165
166
|
@staticmethod
|
|
166
167
|
def confirm(prompt: str, default: bool = True) -> bool:
|
|
167
|
-
"""Display a confirmation prompt"""
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
168
|
+
"""Display a confirmation prompt as a selectable list"""
|
|
169
|
+
choices = [
|
|
170
|
+
questionary.Choice("Yes", value=True),
|
|
171
|
+
questionary.Choice("No", value=False),
|
|
172
|
+
]
|
|
173
|
+
default_choice = choices[0] if default else choices[1]
|
|
174
|
+
|
|
175
|
+
result = questionary.select(
|
|
176
|
+
prompt,
|
|
177
|
+
choices=choices,
|
|
178
|
+
default=default_choice,
|
|
179
|
+
).ask()
|
|
180
|
+
|
|
181
|
+
if result is None:
|
|
182
|
+
raise KeyboardInterrupt
|
|
183
|
+
|
|
184
|
+
return result
|
|
173
185
|
|
|
174
186
|
@staticmethod
|
|
175
187
|
def prompt(message: str, default: Optional[str] = None) -> str:
|
|
176
188
|
"""Display an input prompt"""
|
|
177
|
-
|
|
178
|
-
message = f"{message} [{default}]"
|
|
179
|
-
response = console.input(f"[bold cyan]?[/bold cyan] {message}: ").strip()
|
|
180
|
-
return response or default or ""
|
|
189
|
+
return questionary.text(message, default=default or "").ask()
|
|
181
190
|
|
|
182
191
|
@staticmethod
|
|
183
192
|
def handle_exception(e: Exception):
|