refactor: migrate templates and static files to self-contained modules
Templates Migration: - Migrate admin templates to modules (tenancy, billing, monitoring, marketplace, etc.) - Migrate vendor templates to modules (tenancy, billing, orders, messaging, etc.) - Migrate storefront templates to modules (catalog, customers, orders, cart, checkout, cms) - Migrate public templates to modules (billing, marketplace, cms) - Keep shared templates in app/templates/ (base.html, errors/, partials/, macros/) - Migrate letzshop partials to marketplace module Static Files Migration: - Migrate admin JS to modules: tenancy (23 files), core (5 files), monitoring (1 file) - Migrate vendor JS to modules: tenancy (4 files), core (2 files) - Migrate shared JS: vendor-selector.js to core, media-picker.js to cms - Migrate storefront JS: storefront-layout.js to core - Keep framework JS in static/ (api-client, utils, money, icons, log-config, lib/) - Update all template references to use module_static paths Naming Consistency: - Rename static/platform/ to static/public/ - Rename app/templates/platform/ to app/templates/public/ - Update all extends and static references Documentation: - Update module-system.md with shared templates documentation - Update frontend-structure.md with new module JS organization Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
16
app/modules/core/utils/__init__.py
Normal file
16
app/modules/core/utils/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# app/modules/core/utils/__init__.py
|
||||
"""Core module utilities."""
|
||||
|
||||
from .page_context import (
|
||||
get_admin_context,
|
||||
get_vendor_context,
|
||||
get_storefront_context,
|
||||
get_public_context,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"get_admin_context",
|
||||
"get_vendor_context",
|
||||
"get_storefront_context",
|
||||
"get_public_context",
|
||||
]
|
||||
328
app/modules/core/utils/page_context.py
Normal file
328
app/modules/core/utils/page_context.py
Normal file
@@ -0,0 +1,328 @@
|
||||
# app/modules/core/utils/page_context.py
|
||||
"""
|
||||
Shared page context helpers for HTML page routes.
|
||||
|
||||
These functions build template contexts that include common variables
|
||||
needed across different frontends (admin, vendor, storefront, public).
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import Request
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.config import settings
|
||||
from app.modules.core.services.platform_settings_service import platform_settings_service
|
||||
from app.utils.i18n import get_jinja2_globals
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Vendor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_admin_context(
|
||||
request: Request,
|
||||
current_user: User,
|
||||
db: Session | None = None,
|
||||
**extra_context,
|
||||
) -> dict:
|
||||
"""
|
||||
Build template context for admin dashboard pages.
|
||||
|
||||
Args:
|
||||
request: FastAPI request object
|
||||
current_user: Authenticated admin user
|
||||
db: Optional database session
|
||||
**extra_context: Additional variables for template
|
||||
|
||||
Returns:
|
||||
Dictionary with request, user, and extra context
|
||||
"""
|
||||
context = {
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
}
|
||||
|
||||
if extra_context:
|
||||
context.update(extra_context)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def get_vendor_context(
|
||||
request: Request,
|
||||
db: Session,
|
||||
current_user: User,
|
||||
vendor_code: str,
|
||||
**extra_context,
|
||||
) -> dict:
|
||||
"""
|
||||
Build template context for vendor dashboard pages.
|
||||
|
||||
Resolves locale/currency using the platform settings service with
|
||||
vendor override support:
|
||||
1. Vendor's storefront_locale (if set)
|
||||
2. Platform's default from PlatformSettingsService
|
||||
3. Environment variable
|
||||
4. Hardcoded fallback
|
||||
|
||||
Args:
|
||||
request: FastAPI request object
|
||||
db: Database session
|
||||
current_user: Authenticated vendor user
|
||||
vendor_code: Vendor subdomain/code
|
||||
**extra_context: Additional variables for template
|
||||
|
||||
Returns:
|
||||
Dictionary with request, user, vendor, resolved locale/currency, and extra context
|
||||
"""
|
||||
# Load vendor from database
|
||||
vendor = db.query(Vendor).filter(Vendor.subdomain == vendor_code).first()
|
||||
|
||||
# Get platform defaults
|
||||
platform_config = platform_settings_service.get_storefront_config(db)
|
||||
|
||||
# Resolve with vendor override
|
||||
storefront_locale = platform_config["locale"]
|
||||
storefront_currency = platform_config["currency"]
|
||||
|
||||
if vendor and vendor.storefront_locale:
|
||||
storefront_locale = vendor.storefront_locale
|
||||
|
||||
context = {
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
"vendor": vendor,
|
||||
"vendor_code": vendor_code,
|
||||
"storefront_locale": storefront_locale,
|
||||
"storefront_currency": storefront_currency,
|
||||
"dashboard_language": vendor.dashboard_language if vendor else "en",
|
||||
}
|
||||
|
||||
# Add any extra context
|
||||
if extra_context:
|
||||
context.update(extra_context)
|
||||
|
||||
logger.debug(
|
||||
"[VENDOR_CONTEXT] Context built",
|
||||
extra={
|
||||
"vendor_id": vendor.id if vendor else None,
|
||||
"vendor_code": vendor_code,
|
||||
"storefront_locale": storefront_locale,
|
||||
"storefront_currency": storefront_currency,
|
||||
"extra_keys": list(extra_context.keys()) if extra_context else [],
|
||||
},
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def get_storefront_context(
|
||||
request: Request,
|
||||
db: Session | None = None,
|
||||
**extra_context,
|
||||
) -> dict:
|
||||
"""
|
||||
Build template context for storefront (customer shop) pages.
|
||||
|
||||
Automatically includes vendor and theme from middleware request.state.
|
||||
Additional context can be passed as keyword arguments.
|
||||
|
||||
Args:
|
||||
request: FastAPI request object with vendor/theme in state
|
||||
db: Optional database session for loading navigation pages
|
||||
**extra_context: Additional variables for template (user, product_id, etc.)
|
||||
|
||||
Returns:
|
||||
Dictionary with request, vendor, theme, navigation pages, and extra context
|
||||
"""
|
||||
# Import here to avoid circular imports
|
||||
from app.modules.cms.services import content_page_service
|
||||
|
||||
# Extract from middleware state
|
||||
vendor = getattr(request.state, "vendor", None)
|
||||
platform = getattr(request.state, "platform", None)
|
||||
theme = getattr(request.state, "theme", None)
|
||||
clean_path = getattr(request.state, "clean_path", request.url.path)
|
||||
vendor_context = getattr(request.state, "vendor_context", None)
|
||||
|
||||
# Get platform_id (default to 1 for OMS if not set)
|
||||
platform_id = platform.id if platform else 1
|
||||
|
||||
# Get detection method from vendor_context
|
||||
access_method = (
|
||||
vendor_context.get("detection_method", "unknown")
|
||||
if vendor_context
|
||||
else "unknown"
|
||||
)
|
||||
|
||||
if vendor is None:
|
||||
logger.warning(
|
||||
"[STOREFRONT_CONTEXT] Vendor not found in request.state",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"host": request.headers.get("host", ""),
|
||||
"has_vendor": False,
|
||||
},
|
||||
)
|
||||
|
||||
# Calculate base URL for links
|
||||
# - Domain/subdomain access: base_url = "/"
|
||||
# - Path-based access: base_url = "/vendor/{vendor_code}/" or "/vendors/{vendor_code}/"
|
||||
base_url = "/"
|
||||
if access_method == "path" and vendor:
|
||||
# Use the full_prefix from vendor_context to determine which pattern was used
|
||||
full_prefix = (
|
||||
vendor_context.get("full_prefix", "/vendor/")
|
||||
if vendor_context
|
||||
else "/vendor/"
|
||||
)
|
||||
base_url = f"{full_prefix}{vendor.subdomain}/"
|
||||
|
||||
# Load footer and header navigation pages from CMS if db session provided
|
||||
footer_pages = []
|
||||
header_pages = []
|
||||
if db and vendor:
|
||||
try:
|
||||
vendor_id = vendor.id
|
||||
# Get pages configured to show in footer
|
||||
footer_pages = content_page_service.list_pages_for_vendor(
|
||||
db,
|
||||
platform_id=platform_id,
|
||||
vendor_id=vendor_id,
|
||||
footer_only=True,
|
||||
include_unpublished=False,
|
||||
)
|
||||
# Get pages configured to show in header
|
||||
header_pages = content_page_service.list_pages_for_vendor(
|
||||
db,
|
||||
platform_id=platform_id,
|
||||
vendor_id=vendor_id,
|
||||
header_only=True,
|
||||
include_unpublished=False,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"[STOREFRONT_CONTEXT] Failed to load navigation pages",
|
||||
extra={"error": str(e), "vendor_id": vendor.id if vendor else None},
|
||||
)
|
||||
|
||||
# Resolve storefront locale and currency
|
||||
storefront_config = {"locale": "fr-LU", "currency": "EUR"} # defaults
|
||||
if db and vendor:
|
||||
platform_config = platform_settings_service.get_storefront_config(db)
|
||||
storefront_config["locale"] = platform_config["locale"]
|
||||
storefront_config["currency"] = platform_config["currency"]
|
||||
if vendor.storefront_locale:
|
||||
storefront_config["locale"] = vendor.storefront_locale
|
||||
|
||||
context = {
|
||||
"request": request,
|
||||
"vendor": vendor,
|
||||
"theme": theme,
|
||||
"clean_path": clean_path,
|
||||
"access_method": access_method,
|
||||
"base_url": base_url,
|
||||
"footer_pages": footer_pages,
|
||||
"header_pages": header_pages,
|
||||
"storefront_locale": storefront_config["locale"],
|
||||
"storefront_currency": storefront_config["currency"],
|
||||
}
|
||||
|
||||
# Add any extra context (user, product_id, category_slug, etc.)
|
||||
if extra_context:
|
||||
context.update(extra_context)
|
||||
|
||||
logger.debug(
|
||||
"[STOREFRONT_CONTEXT] Context built",
|
||||
extra={
|
||||
"vendor_id": vendor.id if vendor else None,
|
||||
"vendor_name": vendor.name if vendor else None,
|
||||
"vendor_subdomain": vendor.subdomain if vendor else None,
|
||||
"has_theme": theme is not None,
|
||||
"access_method": access_method,
|
||||
"base_url": base_url,
|
||||
"storefront_locale": storefront_config["locale"],
|
||||
"storefront_currency": storefront_config["currency"],
|
||||
"footer_pages_count": len(footer_pages),
|
||||
"header_pages_count": len(header_pages),
|
||||
"extra_keys": list(extra_context.keys()) if extra_context else [],
|
||||
},
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def get_public_context(
|
||||
request: Request,
|
||||
db: Session,
|
||||
**extra_context,
|
||||
) -> dict:
|
||||
"""
|
||||
Build context for public/marketing pages.
|
||||
|
||||
Includes platform info, i18n globals, and CMS navigation pages.
|
||||
|
||||
Args:
|
||||
request: FastAPI request object
|
||||
db: Database session
|
||||
**extra_context: Additional variables for template
|
||||
|
||||
Returns:
|
||||
Dictionary with request, platform info, i18n globals, and extra context
|
||||
"""
|
||||
# Import here to avoid circular imports
|
||||
from app.modules.cms.services import content_page_service
|
||||
|
||||
# Get language from request state (set by middleware)
|
||||
language = getattr(request.state, "language", "fr")
|
||||
|
||||
# Get platform from middleware (default to OMS platform_id=1)
|
||||
platform = getattr(request.state, "platform", None)
|
||||
platform_id = platform.id if platform else 1
|
||||
|
||||
# Get translation function
|
||||
i18n_globals = get_jinja2_globals(language)
|
||||
|
||||
context = {
|
||||
"request": request,
|
||||
"platform": platform,
|
||||
"platform_name": "Wizamart",
|
||||
"platform_domain": settings.platform_domain,
|
||||
"stripe_publishable_key": settings.stripe_publishable_key,
|
||||
"trial_days": settings.stripe_trial_days,
|
||||
}
|
||||
|
||||
# Add i18n globals (_, t, current_language, SUPPORTED_LANGUAGES, etc.)
|
||||
context.update(i18n_globals)
|
||||
|
||||
# Load CMS pages for header, footer, and legal navigation
|
||||
header_pages = []
|
||||
footer_pages = []
|
||||
legal_pages = []
|
||||
try:
|
||||
# Platform marketing pages (is_platform_page=True)
|
||||
header_pages = content_page_service.list_platform_pages(
|
||||
db, platform_id=platform_id, header_only=True, include_unpublished=False
|
||||
)
|
||||
footer_pages = content_page_service.list_platform_pages(
|
||||
db, platform_id=platform_id, footer_only=True, include_unpublished=False
|
||||
)
|
||||
# For legal pages, we need to add footer support or use a different approach
|
||||
# For now, legal pages come from footer pages with show_in_legal flag
|
||||
legal_pages = [] # Will be handled separately if needed
|
||||
logger.debug(
|
||||
f"Loaded CMS pages: {len(header_pages)} header, {len(footer_pages)} footer, {len(legal_pages)} legal"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load CMS navigation pages: {e}")
|
||||
|
||||
context["header_pages"] = header_pages
|
||||
context["footer_pages"] = footer_pages
|
||||
context["legal_pages"] = legal_pages
|
||||
|
||||
# Add any extra context
|
||||
if extra_context:
|
||||
context.update(extra_context)
|
||||
|
||||
return context
|
||||
Reference in New Issue
Block a user