From 3d3b8cae226b900fe069892ff2cbfbcd0b1001e5 Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Fri, 23 Jan 2026 14:08:02 +0100 Subject: [PATCH] feat: add platform detail/edit admin UI and service enhancements - Add platform detail and edit admin pages with templates and JS - Add ContentPageService methods: list_all_platform_pages, list_all_vendor_defaults - Deprecate /admin/platform-homepage route (redirects to /admin/platforms) - Add migration to fix content_page nullable columns - Refine platform and vendor context middleware - Add platform context middleware unit tests - Update platforms.js with improved functionality - Add section-based homepage plan documentation Co-Authored-By: Claude Opus 4.5 --- ...z4e5f6a7b8c9_add_multi_platform_support.py | 4 +- ...0k1l2_fix_content_page_nullable_columns.py | 115 ++ app/platforms/shared/base_platform.py | 1 - app/routes/admin_pages.py | 18 +- app/routes/platform_pages.py | 7 +- app/services/content_page_service.py | 60 + app/templates/admin/content-pages.html | 3 +- app/templates/admin/partials/sidebar.html | 1 - app/templates/admin/platform-detail.html | 270 +++++ app/templates/admin/platform-edit.html | 324 ++++++ app/templates/admin/platforms.html | 16 +- docs/proposals/TEMP.md | 557 +++++++++ ...rm-cms-architecture-implementation-plan.md | 30 +- docs/proposals/section-based-homepage-plan.md | 392 +++++++ middleware/platform_context.py | 34 +- middleware/vendor_context.py | 22 +- models/database/content_page.py | 14 +- scripts/validate_architecture.py | 2 +- static/admin/js/content-pages.js | 34 +- static/admin/js/platform-detail.js | 139 +++ static/admin/js/platform-edit.js | 230 ++++ static/admin/js/platform-homepage.js | 6 +- static/admin/js/platforms.js | 22 +- .../unit/middleware/test_platform_context.py | 1002 +++++++++++++++++ tests/unit/middleware/test_vendor_context.py | 25 + 25 files changed, 3233 insertions(+), 95 deletions(-) create mode 100644 alembic/versions/z7h8i9j0k1l2_fix_content_page_nullable_columns.py create mode 100644 app/templates/admin/platform-detail.html create mode 100644 app/templates/admin/platform-edit.html create mode 100644 docs/proposals/TEMP.md create mode 100644 docs/proposals/section-based-homepage-plan.md create mode 100644 static/admin/js/platform-detail.js create mode 100644 static/admin/js/platform-edit.js create mode 100644 tests/unit/middleware/test_platform_context.py diff --git a/alembic/versions/z4e5f6a7b8c9_add_multi_platform_support.py b/alembic/versions/z4e5f6a7b8c9_add_multi_platform_support.py index 46920729..185ed0cf 100644 --- a/alembic/versions/z4e5f6a7b8c9_add_multi_platform_support.py +++ b/alembic/versions/z4e5f6a7b8c9_add_multi_platform_support.py @@ -178,9 +178,7 @@ def upgrade() -> None: """) ) - # Get the OMS platform ID - result = conn.execute(sa.text("SELECT id FROM platforms WHERE code = 'oms'")) - oms_platform_id = result.fetchone()[0] +I dn # ========================================================================= # 6. Backfill content_pages with platform_id diff --git a/alembic/versions/z7h8i9j0k1l2_fix_content_page_nullable_columns.py b/alembic/versions/z7h8i9j0k1l2_fix_content_page_nullable_columns.py new file mode 100644 index 00000000..12a0fe1f --- /dev/null +++ b/alembic/versions/z7h8i9j0k1l2_fix_content_page_nullable_columns.py @@ -0,0 +1,115 @@ +"""Fix content_page nullable boolean columns + +Revision ID: z7h8i9j0k1l2 +Revises: z6g7h8i9j0k1 +Create Date: 2026-01-20 + +This migration: +1. Sets NULL values to defaults for boolean and integer columns +2. Alters columns to be NOT NULL +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "z7h8i9j0k1l2" +down_revision = "z6g7h8i9j0k1" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # First, update any NULL values to defaults + op.execute(""" + UPDATE content_pages + SET display_order = 0 + WHERE display_order IS NULL + """) + + op.execute(""" + UPDATE content_pages + SET show_in_footer = true + WHERE show_in_footer IS NULL + """) + + op.execute(""" + UPDATE content_pages + SET show_in_header = false + WHERE show_in_header IS NULL + """) + + op.execute(""" + UPDATE content_pages + SET show_in_legal = false + WHERE show_in_legal IS NULL + """) + + # Now alter columns to be NOT NULL + op.alter_column( + "content_pages", + "display_order", + existing_type=sa.Integer(), + nullable=False, + server_default="0", + ) + + op.alter_column( + "content_pages", + "show_in_footer", + existing_type=sa.Boolean(), + nullable=False, + server_default="true", + ) + + op.alter_column( + "content_pages", + "show_in_header", + existing_type=sa.Boolean(), + nullable=False, + server_default="false", + ) + + op.alter_column( + "content_pages", + "show_in_legal", + existing_type=sa.Boolean(), + nullable=False, + server_default="false", + ) + + +def downgrade() -> None: + # Revert columns to nullable (no server default) + op.alter_column( + "content_pages", + "display_order", + existing_type=sa.Integer(), + nullable=True, + server_default=None, + ) + + op.alter_column( + "content_pages", + "show_in_footer", + existing_type=sa.Boolean(), + nullable=True, + server_default=None, + ) + + op.alter_column( + "content_pages", + "show_in_header", + existing_type=sa.Boolean(), + nullable=True, + server_default=None, + ) + + op.alter_column( + "content_pages", + "show_in_legal", + existing_type=sa.Boolean(), + nullable=True, + server_default=None, + ) diff --git a/app/platforms/shared/base_platform.py b/app/platforms/shared/base_platform.py index b3d5d1f3..f8d20c2b 100644 --- a/app/platforms/shared/base_platform.py +++ b/app/platforms/shared/base_platform.py @@ -69,7 +69,6 @@ class BasePlatformConfig(ABC): """ return [ "home", - "platform_homepage", "pricing", "about", "contact", diff --git a/app/routes/admin_pages.py b/app/routes/admin_pages.py index 89b1cd6e..b03d5dda 100644 --- a/app/routes/admin_pages.py +++ b/app/routes/admin_pages.py @@ -1116,23 +1116,21 @@ async def admin_platform_edit( # ============================================================================ -@router.get("/platform-homepage", response_class=HTMLResponse, include_in_schema=False) +@router.get("/platform-homepage", include_in_schema=False) async def admin_platform_homepage_manager( request: Request, current_user: User = Depends(get_current_admin_from_cookie_or_header), db: Session = Depends(get_db), ): """ - Render platform homepage manager. - Allows editing the main platform homepage with template selection. + Deprecated: Redirects to platforms page. + + Platform homepages are now managed via: + - /admin/platforms → Select platform → Homepage button + - Or directly: /admin/content-pages?platform_code={code}&slug=home """ - return templates.TemplateResponse( - "admin/platform-homepage.html", - { - "request": request, - "user": current_user, - }, - ) + from starlette.responses import RedirectResponse + return RedirectResponse(url="/admin/platforms", status_code=302) @router.get("/content-pages", response_class=HTMLResponse, include_in_schema=False) diff --git a/app/routes/platform_pages.py b/app/routes/platform_pages.py index 1b7fea18..1fc045f8 100644 --- a/app/routes/platform_pages.py +++ b/app/routes/platform_pages.py @@ -158,18 +158,13 @@ async def homepage( return RedirectResponse(url="/shop/", status_code=302) # Scenario 2: Platform marketing site (no vendor) - # Try to load platform homepage from CMS + # Load platform homepage from CMS (slug='home') platform_id = platform.id if platform else 1 cms_homepage = content_page_service.get_platform_page( db, platform_id=platform_id, slug="home", include_unpublished=False ) - if not cms_homepage: - cms_homepage = content_page_service.get_platform_page( - db, platform_id=platform_id, slug="platform_homepage", include_unpublished=False - ) - if cms_homepage: # Use CMS-based homepage with template selection context = get_platform_context(request, db) diff --git a/app/services/content_page_service.py b/app/services/content_page_service.py index 816be7b0..c94edbb4 100644 --- a/app/services/content_page_service.py +++ b/app/services/content_page_service.py @@ -305,6 +305,66 @@ class ContentPageService: .all() ) + @staticmethod + def list_all_platform_pages( + db: Session, + include_unpublished: bool = False, + ) -> list[ContentPage]: + """ + List all platform marketing pages across all platforms (for admin use). + + Args: + db: Database session + include_unpublished: Include draft pages + + Returns: + List of all platform marketing ContentPage objects + """ + filters = [ + ContentPage.vendor_id.is_(None), + ContentPage.is_platform_page.is_(True), + ] + + if not include_unpublished: + filters.append(ContentPage.is_published.is_(True)) + + return ( + db.query(ContentPage) + .filter(and_(*filters)) + .order_by(ContentPage.platform_id, ContentPage.display_order, ContentPage.title) + .all() + ) + + @staticmethod + def list_all_vendor_defaults( + db: Session, + include_unpublished: bool = False, + ) -> list[ContentPage]: + """ + List all vendor default pages across all platforms (for admin use). + + Args: + db: Database session + include_unpublished: Include draft pages + + Returns: + List of all vendor default ContentPage objects + """ + filters = [ + ContentPage.vendor_id.is_(None), + ContentPage.is_platform_page.is_(False), + ] + + if not include_unpublished: + filters.append(ContentPage.is_published.is_(True)) + + return ( + db.query(ContentPage) + .filter(and_(*filters)) + .order_by(ContentPage.platform_id, ContentPage.display_order, ContentPage.title) + .all() + ) + # ========================================================================= # CRUD Methods # ========================================================================= diff --git a/app/templates/admin/content-pages.html b/app/templates/admin/content-pages.html index 09e44c5d..a38d1f44 100644 --- a/app/templates/admin/content-pages.html +++ b/app/templates/admin/content-pages.html @@ -32,7 +32,8 @@