feat: implement self-contained module architecture (Phase 1 & 2)
Phase 1 - Foundation: - Add app/modules/contracts/ with Protocol definitions for cross-module communication (ServiceProtocol, ContentServiceProtocol, MediaServiceProtocol) - Enhance app/modules/base.py ModuleDefinition with self-contained module support (is_self_contained, services_path, models_path, etc.) - Update app/templates_config.py with multi-directory template loading using Jinja2 ChoiceLoader for module templates Phase 2 - CMS Pilot Module: - Migrate CMS service to app/modules/cms/services/content_page_service.py - Create app/modules/cms/exceptions.py with CMS-specific exceptions - Configure app/modules/cms/models/ to re-export ContentPage from canonical location (models.database) to avoid circular imports - Update cms_module definition with is_self_contained=True and paths - Add backwards compatibility shims with deprecation warnings: - app/services/content_page_service.py -> app.modules.cms.services - app/exceptions/content_page.py -> app.modules.cms.exceptions Note: SQLAlchemy models remain in models/database/ as the canonical location to avoid circular imports at startup time. Module model packages re-export from the canonical location. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,10 +4,21 @@ Shared Jinja2 templates configuration.
|
||||
|
||||
All route modules should import `templates` from here to ensure
|
||||
consistent globals (like translation function) are available.
|
||||
|
||||
Template Loading Strategy:
|
||||
- Core templates from app/templates/ (highest priority)
|
||||
- Module templates from app/modules/<module>/templates/ (namespaced)
|
||||
|
||||
Module templates should use namespace prefix to avoid collisions:
|
||||
app/modules/cms/templates/cms/admin/pages.html
|
||||
-> Rendered as: templates.TemplateResponse("cms/admin/pages.html", ...)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from jinja2 import ChoiceLoader, FileSystemLoader
|
||||
|
||||
from app.utils.i18n import (
|
||||
LANGUAGE_FLAGS,
|
||||
@@ -17,11 +28,60 @@ from app.utils.i18n import (
|
||||
create_translation_context,
|
||||
)
|
||||
|
||||
# Templates directory
|
||||
TEMPLATES_DIR = Path(__file__).parent / "templates"
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Create shared templates instance
|
||||
# Core templates directory
|
||||
TEMPLATES_DIR = Path(__file__).parent / "templates"
|
||||
MODULES_DIR = Path(__file__).parent / "modules"
|
||||
|
||||
|
||||
def create_template_loaders() -> ChoiceLoader:
|
||||
"""
|
||||
Create a ChoiceLoader that searches multiple template directories.
|
||||
|
||||
Search order:
|
||||
1. Core templates (app/templates/) - highest priority
|
||||
2. Module templates (app/modules/<module>/templates/) - in alphabetical order
|
||||
|
||||
Returns:
|
||||
ChoiceLoader configured with all template directories
|
||||
"""
|
||||
loaders = [FileSystemLoader(str(TEMPLATES_DIR))] # Core templates first
|
||||
|
||||
# Add module template directories
|
||||
if MODULES_DIR.exists():
|
||||
for module_dir in sorted(MODULES_DIR.iterdir()):
|
||||
if module_dir.is_dir():
|
||||
templates_path = module_dir / "templates"
|
||||
if templates_path.exists() and templates_path.is_dir():
|
||||
loaders.append(FileSystemLoader(str(templates_path)))
|
||||
logger.debug(f"[Templates] Added module templates: {module_dir.name}")
|
||||
|
||||
return ChoiceLoader(loaders)
|
||||
|
||||
|
||||
def get_module_template_dirs() -> list[Path]:
|
||||
"""
|
||||
Get list of all module template directories.
|
||||
|
||||
Useful for debugging and introspection.
|
||||
|
||||
Returns:
|
||||
List of Path objects for module template directories
|
||||
"""
|
||||
dirs = []
|
||||
if MODULES_DIR.exists():
|
||||
for module_dir in sorted(MODULES_DIR.iterdir()):
|
||||
if module_dir.is_dir():
|
||||
templates_path = module_dir / "templates"
|
||||
if templates_path.exists() and templates_path.is_dir():
|
||||
dirs.append(templates_path)
|
||||
return dirs
|
||||
|
||||
|
||||
# Create shared templates instance with multi-directory loader
|
||||
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
|
||||
templates.env.loader = create_template_loaders()
|
||||
|
||||
# Add translation function to Jinja2 environment globals
|
||||
# This makes _() available in all templates AND macros
|
||||
|
||||
Reference in New Issue
Block a user