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:
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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"},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user