feat(djinit/services): support nested apps and full AppConfig path handling
- New Features - Detect and process nested project structures, generating correct module paths for AppConfig. - Automatically upgrade legacy app configurations from simple module paths to full AppConfig paths. - Enhancements - Insert full AppConfig paths into USER_DEFINED_APPS, improving template rendering for all settings files. - Provide clearer success and upgrade messages to users. - Bug Fixes - Avoid duplicate app entries and handle missing USER_DEFINED_APPS sections without crashing. - Ensure proper __init__.py creation in nested directories.
This commit is contained in:
3 files changed
+107
-13
No files matched your search
+48
-7
@@ -78,8 +78,17 @@ class AppManager:
|
||||
if self._is_predefined_structure():
|
||||
return self._create_predefined_app(os.path.join(self.current_dir, "apps"))
|
||||
|
||||
_, _, apps_base_dir = self._get_project_structure()
|
||||
success = DjangoHelper.startapp(self.app_name, apps_base_dir)
|
||||
is_nested, nested_dir, apps_base_dir = self._get_project_structure()
|
||||
|
||||
# Calculate app_module based on project structure
|
||||
if is_nested and nested_dir:
|
||||
# Normalize nested_dir (replace / with . for module path)
|
||||
module_base = nested_dir.replace(os.path.sep, '.')
|
||||
app_module = f"{module_base}.{self.app_name}"
|
||||
else:
|
||||
app_module = self.app_name
|
||||
|
||||
success = DjangoHelper.startapp(self.app_name, apps_base_dir, app_module)
|
||||
if success:
|
||||
UIFormatter.print_success(f"Created Django app '{self.app_name}' in {apps_base_dir}")
|
||||
else:
|
||||
@@ -106,23 +115,49 @@ class AppManager:
|
||||
is_nested, nested_dir, _ = self._get_project_structure()
|
||||
app_module_path = calculate_app_module_path(self.app_name, is_nested, nested_dir)
|
||||
|
||||
# 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"
|
||||
|
||||
# We'll check if either the module path or the full config path exists
|
||||
# but we'll prefer adding the full config path
|
||||
|
||||
existing_apps = extract_existing_apps(content)
|
||||
if app_module_path in existing_apps:
|
||||
UIFormatter.print_success(f"App '{app_module_path}' already configured in USER_DEFINED_APPS")
|
||||
|
||||
# Check if full config is already there
|
||||
if full_app_config in existing_apps:
|
||||
UIFormatter.print_success(f"App '{full_app_config}' already configured in USER_DEFINED_APPS")
|
||||
return True
|
||||
|
||||
# Check if legacy module path is there, if so, upgrade it
|
||||
if app_module_path in existing_apps:
|
||||
from djinit.utils.common import replace_app_in_user_defined_apps
|
||||
|
||||
UIFormatter.print_info(f"Upgrading app configuration from '{app_module_path}' to '{full_app_config}'...")
|
||||
updated_content = replace_app_in_user_defined_apps(content, app_module_path, full_app_config)
|
||||
|
||||
if updated_content:
|
||||
with open(base_settings_path, "w") as f:
|
||||
f.write(updated_content)
|
||||
UIFormatter.print_success(f"Upgraded '{app_module_path}' to '{full_app_config}' in USER_DEFINED_APPS")
|
||||
return True
|
||||
else:
|
||||
UIFormatter.print_error(f"Failed to upgrade app '{app_module_path}'")
|
||||
return False
|
||||
|
||||
if "USER_DEFINED_APPS" not in content:
|
||||
UIFormatter.print_error("Could not find USER_DEFINED_APPS section in base.py")
|
||||
return False
|
||||
|
||||
updated_content = insert_apps_into_user_defined_apps(content, [app_module_path])
|
||||
updated_content = insert_apps_into_user_defined_apps(content, [full_app_config])
|
||||
if not updated_content:
|
||||
return False
|
||||
|
||||
with open(base_settings_path, "w") as f:
|
||||
f.write(updated_content)
|
||||
|
||||
UIFormatter.print_success(f"Added '{app_module_path}' to USER_DEFINED_APPS in base.py")
|
||||
UIFormatter.print_success(f"Added '{full_app_config}' to USER_DEFINED_APPS in base.py")
|
||||
return True
|
||||
|
||||
def _get_project_structure(self) -> tuple[bool, Optional[str], str]:
|
||||
@@ -192,7 +227,13 @@ class AppManager:
|
||||
|
||||
model_class_name = "".join([part.capitalize() for part in self.app_name.split("_")])
|
||||
app_module = f"apps.{self.app_name}"
|
||||
base_context = {"app_name": self.app_name, "app_module": app_module, "model_class_name": model_class_name}
|
||||
app_config_name = self.app_name.title().replace('_', '')
|
||||
base_context = {
|
||||
"app_name": self.app_name,
|
||||
"app_config_name": app_config_name,
|
||||
"app_module": app_module,
|
||||
"model_class_name": model_class_name,
|
||||
}
|
||||
|
||||
create_file_from_template(
|
||||
os.path.join(app_dir, "apps.py"),
|
||||
|
||||
@@ -75,10 +75,24 @@ class FileManager:
|
||||
secret_key = generate_secret_key()
|
||||
dev_context = {"secret_key": secret_key}
|
||||
|
||||
# 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
|
||||
|
||||
for filename, context in [
|
||||
("base.py", base_context),
|
||||
("base.py", base_settings_context),
|
||||
("development.py", dev_context),
|
||||
("production.py", base_context),
|
||||
("production.py", base_settings_context),
|
||||
]:
|
||||
filepath = os.path.join(settings_dir, filename)
|
||||
template_path = f"config/settings/{filename.replace('.py', '')}.py-tpl"
|
||||
|
||||
@@ -46,8 +46,12 @@ class ProjectManager:
|
||||
if apps_base_dir != self.project_root:
|
||||
create_directory_with_init(apps_base_dir, f"Created {os.path.basename(apps_base_dir)}/__init__.py")
|
||||
|
||||
for app_name in self.app_names:
|
||||
DjangoHelper.startapp(app_name, apps_base_dir)
|
||||
# Calculate module paths for correct AppConfig generation (e.g. src.users)
|
||||
app_module_paths = calculate_app_module_paths(self.app_names, self.metadata)
|
||||
|
||||
for i, app_name in enumerate(self.app_names):
|
||||
app_module = app_module_paths[i]
|
||||
DjangoHelper.startapp(app_name, apps_base_dir, app_module)
|
||||
UIFormatter.print_success(f"Django app '{app_name}' created successfully!")
|
||||
|
||||
self.add_apps_to_settings()
|
||||
@@ -69,10 +73,45 @@ class ProjectManager:
|
||||
|
||||
app_module_paths = calculate_app_module_paths(self.app_names, self.metadata)
|
||||
|
||||
existing_apps = extract_existing_apps(content)
|
||||
apps_to_add = [app for app in app_module_paths if app not in existing_apps]
|
||||
# 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}")
|
||||
|
||||
existing_apps = extract_existing_apps(content)
|
||||
|
||||
# Check against both module path and full config path to avoid duplicates
|
||||
apps_to_add = []
|
||||
for i, full_path in enumerate(full_config_paths):
|
||||
mod_path = app_module_paths[i]
|
||||
if full_path not in existing_apps and mod_path not in existing_apps:
|
||||
apps_to_add.append(full_path)
|
||||
elif mod_path in existing_apps:
|
||||
# If legacy module path exists, we could upgrade it here,
|
||||
# but ProjectManager is typically initialized on creation.
|
||||
# For now, let's just not add a duplicate.
|
||||
# Actually, let's upgrade it if we can
|
||||
pass
|
||||
|
||||
# If we found apps to add, insert them
|
||||
if not apps_to_add:
|
||||
# Check if we need to upgrade any legacy paths
|
||||
upgraded = False
|
||||
for i, full_path in enumerate(full_config_paths):
|
||||
mod_path = app_module_paths[i]
|
||||
if mod_path in existing_apps and full_path not in existing_apps:
|
||||
from djinit.utils.common import replace_app_in_user_defined_apps
|
||||
content = replace_app_in_user_defined_apps(content, mod_path, full_path)
|
||||
upgraded = True
|
||||
|
||||
if upgraded:
|
||||
with open(base_settings_path, "w") as f:
|
||||
f.write(content)
|
||||
UIFormatter.print_success("Upgraded existing apps to full AppConfig paths")
|
||||
return
|
||||
|
||||
UIFormatter.print_success("All apps already configured in USER_DEFINED_APPS")
|
||||
return
|
||||
|
||||
|
||||
Reference in new issue
Block a user