diff --git a/src/djinit/creators/app.py b/src/djinit/creators/app.py index ec8efa8..732951a 100644 --- a/src/djinit/creators/app.py +++ b/src/djinit/creators/app.py @@ -47,8 +47,11 @@ class AppCreator(BaseService): if not self._add_to_installed_apps(): return False + # Register URLs based on structure if structure_type == "predefined": self._add_to_api_v1_urls(self.app_name) + elif structure_type in ["standard", "single"]: + self._add_to_project_urls(self.app_name, structure_type) UIFormatter.print_success(f"Django app '{self.app_name}' created and configured successfully!") return True @@ -165,10 +168,22 @@ class AppCreator(BaseService): # Calculate the app module path based on structure app_module_path = self._calculate_app_module_path(structure_type) - # Use full AppConfig path to properly support nested apps - # e.g. apps.users -> apps.users.apps.UsersConfig - app_config_name = self.app_name.title().replace("_", "") - full_app_config = f"{app_module_path}.apps.{app_config_name}Config" + # Use centralized logic for AppConfig path + is_nested, nested_dir, _ = self._get_project_structure() + nested = structure_type in ["unified", "predefined"] or (structure_type == "standard" and is_nested) + + # Determine the effective nested_dir for config path + if structure_type in ["unified", "predefined"]: + effective_nested_dir = "apps" + elif structure_type == "single": + # For single structure, app is inside the project module + project_dir, _ = CommonUtils.find_project_dir(self.current_dir) + effective_nested_dir = os.path.basename(project_dir) if project_dir else None + nested = True + else: + effective_nested_dir = nested_dir + + full_app_config = CommonUtils.get_full_app_config_path(self.app_name, nested, effective_nested_dir) # We'll check if either the module path or the full config path exists # but we'll prefer adding the full config path @@ -281,36 +296,63 @@ class AppCreator(BaseService): return success - def _add_to_api_v1_urls(self, app_name: str) -> None: - api_v1_urls = os.path.join(self.current_dir, "api", "v1", "urls.py") - if not os.path.exists(api_v1_urls): + def _add_to_project_urls(self, app_name: str, structure_type: str) -> None: + """Add app URLs to the main project urls.py.""" + project_dir, settings_base_path = CommonUtils.find_project_dir(self.current_dir) + if not project_dir: return + + project_urls = os.path.join(project_dir, "urls.py") + if not os.path.exists(project_urls): + return + + app_module_path = self._calculate_app_module_path(structure_type) + include_stmt = f'path("", include("{app_module_path}.urls")),' + try: - with open(api_v1_urls, encoding="utf-8") as f: + with open(project_urls, encoding="utf-8") as f: content = f.read() - import_line = "from django.urls import include, path" - if import_line not in content: - # ensure base import exists (fallback) - content = import_line + "\n\n" + content - - include_stmt = f'path("{app_name}/", include("apps.{app_name}.urls")),' if include_stmt in content: return if "urlpatterns = [" in content: - parts = content.split("urlpatterns = [", 1) - before = parts[0] - rest = parts[1] - if "]" in rest: - idx = rest.rfind("]") - new_rest = rest[:idx] - if not new_rest.endswith("\n"): - new_rest += "\n" - new_rest += f" {include_stmt}\n" + rest[idx:] - content = before + "urlpatterns = [" + new_rest + # Find the best place to insert (before the closing bracket of urlpatterns) + idx = content.rfind("]") + if idx != -1: + content = content[:idx] + f" {include_stmt}\n" + content[idx:] + + with open(project_urls, "w", encoding="utf-8") as f: + f.write(content) + + UIFormatter.print_success(f"Added '{app_name}' URLs to {os.path.basename(project_urls)}") + except Exception as e: + UIFormatter.print_warning(f"Could not automatically update {os.path.basename(project_urls)}: {e}") + + def _add_to_api_v1_urls(self, app_name: str) -> None: + api_v1_urls = os.path.join(self.current_dir, "api", "v1", "urls.py") + if not os.path.exists(api_v1_urls): + return + + structure_type = self._get_structure_type() + app_module_path = self._calculate_app_module_path(structure_type) + + try: + with open(api_v1_urls, encoding="utf-8") as f: + content = f.read() + + include_stmt = f'path("{app_name}/", include("{app_module_path}.urls")),' + if include_stmt in content: + return + + if "urlpatterns = [" in content: + idx = content.rfind("]") + if idx != -1: + content = content[:idx] + f" {include_stmt}\n" + content[idx:] with open(api_v1_urls, "w", encoding="utf-8") as f: f.write(content) + + UIFormatter.print_success(f"Added '{app_name}' URLs to api/v1/urls.py") except Exception: return diff --git a/src/djinit/creators/files.py b/src/djinit/creators/files.py index 7fd53ce..84dcf60 100644 --- a/src/djinit/creators/files.py +++ b/src/djinit/creators/files.py @@ -81,16 +81,12 @@ class FileCreator(BaseService): # Prepare context for base.py with full AppConfig paths base_settings_context = base_context.copy() if "app_names" in base_settings_context: - transformed_apps = [] - for app in base_settings_context["app_names"]: - # Transform to full AppConfig path: module.path -> module.path.apps.ConfigName - short_name = app.split(".")[-1] - config_name = short_name.title().replace("_", "") + "Config" - # If it's the special "apps" container in unified structure, we might want to skip or handle differently - # But assumes all user apps follow standard djinit structure - full_path = f"{app}.apps.{config_name}" - transformed_apps.append(full_path) - base_settings_context["app_names"] = transformed_apps + nested = bool(self.metadata.get("nested_apps")) + nested_dir = self.metadata.get("nested_dir") + base_settings_context["app_names"] = [ + CommonUtils.get_full_app_config_path(name, nested, nested_dir) + for name in base_settings_context["app_names"] + ] for filename, context in [ ("base.py", base_settings_context), @@ -159,7 +155,7 @@ class FileCreator(BaseService): def create_env_file(self) -> None: """Create .env.sample file with environment variables.""" context = { - "project_name": self.module_name, # Use module_name here for settings path + "project_name": self.module_name, # Use module_name here for settings path "use_database_url": self.metadata.get("use_database_url", True), "database_type": self.metadata.get("database_type", "postgresql"), } @@ -245,7 +241,7 @@ class FileCreator(BaseService): CommonUtils.create_file_from_template( api_v1_urls_path, "config/urls.py-tpl", - {"url_type": "api_version", "app_name": "v1", "app_list": [], "app_module": "apps"}, + {"url_type": "api_version", "app_name": "v1", "app_list": self.app_names, "app_module": "apps"}, "Created api/v1/urls.py", ) diff --git a/src/djinit/creators/project.py b/src/djinit/creators/project.py index 57ac51b..c150dbb 100644 --- a/src/djinit/creators/project.py +++ b/src/djinit/creators/project.py @@ -70,14 +70,10 @@ class ProjectCreator(BaseService): raise ConfigError("Could not read base.py settings file") + nested = bool(self.metadata.get("nested_apps")) + nested_dir = self.metadata.get("nested_dir") app_module_paths = CommonUtils.calculate_app_module_paths(self.app_names, self.metadata) - - # Transform to full AppConfig paths (e.g. apps.users.apps.UsersConfig) - full_config_paths = [] - for app in app_module_paths: - short_name = app.split(".")[-1] - config_name = short_name.title().replace("_", "") + "Config" - full_config_paths.append(f"{app}.apps.{config_name}") + full_config_paths = [CommonUtils.get_full_app_config_path(name, nested, nested_dir) for name in self.app_names] existing_apps = CommonUtils.extract_existing_apps(content)