feat: add show_in_legal to admin content page editor

- Add "Show in Legal" checkbox to content page editor UI
- Update API schemas (ContentPageCreate, ContentPageUpdate, ContentPageResponse)
- Add show_in_legal parameter to service methods (create_page, update_page, etc.)
- Fix ContentPageNotFoundException to pass identifier correctly
- Fix UnauthorizedContentPageAccessException to use correct AuthorizationException API
- Add comprehensive unit tests for ContentPageService (35 tests)
- Add content page fixtures for testing
- Update CMS documentation with navigation categories diagram

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-28 20:27:20 +01:00
parent bd447ae7f2
commit 592a4fd7c2
9 changed files with 838 additions and 30 deletions

View File

@@ -52,6 +52,9 @@ class ContentPageCreate(BaseModel):
is_published: bool = Field(default=False, description="Publish immediately")
show_in_footer: bool = Field(default=True, description="Show in footer navigation")
show_in_header: bool = Field(default=False, description="Show in header navigation")
show_in_legal: bool = Field(
default=False, description="Show in legal/bottom bar (next to copyright)"
)
display_order: int = Field(default=0, description="Display order (lower = first)")
vendor_id: int | None = Field(
None, description="Vendor ID (None for platform default)"
@@ -70,6 +73,7 @@ class ContentPageUpdate(BaseModel):
is_published: bool | None = None
show_in_footer: bool | None = None
show_in_header: bool | None = None
show_in_legal: bool | None = None
display_order: int | None = None
@@ -83,6 +87,7 @@ class ContentPageResponse(BaseModel):
title: str
content: str
content_format: str
template: str | None = None
meta_description: str | None
meta_keywords: str | None
is_published: bool
@@ -90,6 +95,7 @@ class ContentPageResponse(BaseModel):
display_order: int
show_in_footer: bool
show_in_header: bool
show_in_legal: bool
is_platform_default: bool
is_vendor_override: bool
created_at: str
@@ -146,6 +152,7 @@ def create_platform_page(
is_published=page_data.is_published,
show_in_footer=page_data.show_in_footer,
show_in_header=page_data.show_in_header,
show_in_legal=page_data.show_in_legal,
display_order=page_data.display_order,
created_by=current_user.id,
)
@@ -209,6 +216,7 @@ def update_page(
is_published=page_data.is_published,
show_in_footer=page_data.show_in_footer,
show_in_header=page_data.show_in_header,
show_in_legal=page_data.show_in_legal,
display_order=page_data.display_order,
updated_by=current_user.id,
)

View File

@@ -23,7 +23,11 @@ class ContentPageNotFoundException(ResourceNotFoundException):
message = f"Content page not found: {identifier}"
else:
message = "Content page not found"
super().__init__(message=message, resource_type="content_page")
super().__init__(
message=message,
resource_type="content_page",
identifier=str(identifier) if identifier else "unknown",
)
class ContentPageAlreadyExistsException(ConflictException):
@@ -61,7 +65,8 @@ class UnauthorizedContentPageAccessException(AuthorizationException):
def __init__(self, action: str = "access"):
super().__init__(
message=f"Cannot {action} content pages from other vendors",
required_permission=f"content_page:{action}",
error_code="CONTENT_PAGE_ACCESS_DENIED",
details={"required_permission": f"content_page:{action}"},
)
@@ -71,7 +76,8 @@ class VendorNotAssociatedException(AuthorizationException):
def __init__(self):
super().__init__(
message="User is not associated with a vendor",
required_permission="vendor:member",
error_code="VENDOR_NOT_ASSOCIATED",
details={"required_permission": "vendor:member"},
)

View File

@@ -172,6 +172,7 @@ class ContentPageService:
is_published: bool = False,
show_in_footer: bool = True,
show_in_header: bool = False,
show_in_legal: bool = False,
display_order: int = 0,
created_by: int | None = None,
) -> ContentPage:
@@ -191,6 +192,7 @@ class ContentPageService:
is_published: Publish immediately
show_in_footer: Show in footer navigation
show_in_header: Show in header navigation
show_in_legal: Show in legal/bottom bar navigation
display_order: Sort order
created_by: User ID who created it
@@ -210,6 +212,7 @@ class ContentPageService:
published_at=datetime.now(UTC) if is_published else None,
show_in_footer=show_in_footer,
show_in_header=show_in_header,
show_in_legal=show_in_legal,
display_order=display_order,
created_by=created_by,
updated_by=created_by,
@@ -237,6 +240,7 @@ class ContentPageService:
is_published: bool | None = None,
show_in_footer: bool | None = None,
show_in_header: bool | None = None,
show_in_legal: bool | None = None,
display_order: int | None = None,
updated_by: int | None = None,
) -> ContentPage | None:
@@ -255,6 +259,7 @@ class ContentPageService:
is_published: New publish status
show_in_footer: New footer visibility
show_in_header: New header visibility
show_in_legal: New legal bar visibility
display_order: New sort order
updated_by: User ID who updated it
@@ -288,6 +293,8 @@ class ContentPageService:
page.show_in_footer = show_in_footer
if show_in_header is not None:
page.show_in_header = show_in_header
if show_in_legal is not None:
page.show_in_legal = show_in_legal
if display_order is not None:
page.display_order = display_order
if updated_by is not None:
@@ -390,6 +397,7 @@ class ContentPageService:
is_published: bool | None = None,
show_in_footer: bool | None = None,
show_in_header: bool | None = None,
show_in_legal: bool | None = None,
display_order: int | None = None,
updated_by: int | None = None,
) -> ContentPage:
@@ -411,6 +419,7 @@ class ContentPageService:
is_published=is_published,
show_in_footer=show_in_footer,
show_in_header=show_in_header,
show_in_legal=show_in_legal,
display_order=display_order,
updated_by=updated_by,
)
@@ -443,6 +452,7 @@ class ContentPageService:
is_published: bool | None = None,
show_in_footer: bool | None = None,
show_in_header: bool | None = None,
show_in_legal: bool | None = None,
display_order: int | None = None,
updated_by: int | None = None,
) -> ContentPage:
@@ -478,6 +488,7 @@ class ContentPageService:
is_published=is_published,
show_in_footer=show_in_footer,
show_in_header=show_in_header,
show_in_legal=show_in_legal,
display_order=display_order,
updated_by=updated_by,
)

View File

@@ -189,7 +189,7 @@
Navigation & Display
</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<!-- Display Order -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
@@ -226,6 +226,21 @@
</span>
</label>
</div>
<!-- Show in Legal (Bottom Bar) -->
<div class="flex items-center">
<label class="flex items-center cursor-pointer">
<input
type="checkbox"
x-model="form.show_in_legal"
class="w-5 h-5 text-purple-600 border-gray-300 rounded focus:ring-purple-500"
>
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-white">
Show in Legal
</span>
</label>
<span class="ml-2 text-xs text-gray-500 dark:text-gray-400" title="Bottom bar next to copyright">(?)</span>
</div>
</div>
</div>