Enhance public access and error handling in site-related views and loaders
- Updated `DebugScopedRateThrottle` to allow public access for blueprint list requests with site filters. - Modified `SiteViewSet` and `SiteBlueprintViewSet` to permit public read access for list requests. - Enhanced `loadSiteDefinition` to resolve site slugs to IDs, improving the loading process for site definitions. - Improved error handling in `SiteDefinitionView` and `loadSiteDefinition` for better user feedback. - Adjusted CSS styles for better layout and alignment in shared components.
This commit is contained in:
@@ -81,8 +81,10 @@ class CreditBalanceViewSet(viewsets.ViewSet):
|
||||
'credits_remaining': credits_remaining,
|
||||
}
|
||||
|
||||
serializer = CreditBalanceSerializer(data)
|
||||
return success_response(data=serializer.data, request=request)
|
||||
# Validate and serialize data
|
||||
serializer = CreditBalanceSerializer(data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return success_response(data=serializer.validated_data, request=request)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
|
||||
@@ -219,61 +219,104 @@ class SiteDefinitionView(APIView):
|
||||
|
||||
GET /api/v1/publisher/sites/{site_id}/definition/
|
||||
"""
|
||||
sites_data_path = os.getenv('SITES_DATA_PATH', '/data/app/sites-data')
|
||||
|
||||
# Try to find latest deployed version
|
||||
site_dir = Path(sites_data_path) / 'clients' / str(site_id)
|
||||
|
||||
if not site_dir.exists():
|
||||
return error_response(
|
||||
f'Site {site_id} not found',
|
||||
status.HTTP_404_NOT_FOUND,
|
||||
request
|
||||
)
|
||||
|
||||
# Look for latest version (check for 'latest' symlink or highest version number)
|
||||
latest_path = site_dir / 'latest' / 'site.json'
|
||||
if latest_path.exists():
|
||||
try:
|
||||
sites_data_path = os.getenv('SITES_DATA_PATH', '/data/app/sites-data')
|
||||
|
||||
# Try to find latest deployed version
|
||||
site_dir = Path(sites_data_path) / 'clients' / str(site_id)
|
||||
|
||||
if not site_dir.exists():
|
||||
return error_response(
|
||||
error=f'Site {site_id} not found at {site_dir}. Site may not be deployed yet.',
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
request=request
|
||||
)
|
||||
|
||||
# Look for latest version (check for 'latest' symlink or highest version number)
|
||||
latest_path = site_dir / 'latest' / 'site.json'
|
||||
if latest_path.exists():
|
||||
try:
|
||||
with open(latest_path, 'r', encoding='utf-8') as f:
|
||||
definition = json.load(f)
|
||||
return Response(definition, status=status.HTTP_200_OK)
|
||||
except json.JSONDecodeError as e:
|
||||
return error_response(
|
||||
error=f'Invalid JSON in site definition file: {str(e)}',
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
request=request
|
||||
)
|
||||
except Exception as e:
|
||||
return error_response(
|
||||
error=f'Failed to load site definition from {latest_path}: {str(e)}',
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
request=request
|
||||
)
|
||||
|
||||
# Fallback: find highest version number
|
||||
try:
|
||||
with open(latest_path, 'r', encoding='utf-8') as f:
|
||||
definition = json.load(f)
|
||||
return Response(definition, status=status.HTTP_200_OK)
|
||||
version_dirs = [d for d in site_dir.iterdir() if d.is_dir() and d.name.startswith('v')]
|
||||
except PermissionError as e:
|
||||
return error_response(
|
||||
error=f'Permission denied accessing site directory: {str(e)}',
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
request=request
|
||||
)
|
||||
except Exception as e:
|
||||
return error_response(
|
||||
f'Failed to load site definition: {str(e)}',
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
request
|
||||
error=f'Error reading site directory: {str(e)}',
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
request=request
|
||||
)
|
||||
|
||||
if not version_dirs:
|
||||
return error_response(
|
||||
error=f'No deployed versions found for site {site_id}. Site may not be deployed yet.',
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
request=request
|
||||
)
|
||||
|
||||
# Sort by version number (extract number from 'v1', 'v2', etc.)
|
||||
try:
|
||||
version_dirs.sort(key=lambda d: int(d.name[1:]) if d.name[1:].isdigit() else 0, reverse=True)
|
||||
latest_version_dir = version_dirs[0]
|
||||
site_json_path = latest_version_dir / 'site.json'
|
||||
|
||||
if site_json_path.exists():
|
||||
with open(site_json_path, 'r', encoding='utf-8') as f:
|
||||
definition = json.load(f)
|
||||
return Response(definition, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return error_response(
|
||||
error=f'Site definition file not found at {site_json_path}',
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
request=request
|
||||
)
|
||||
except json.JSONDecodeError as e:
|
||||
return error_response(
|
||||
error=f'Invalid JSON in site definition file: {str(e)}',
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
request=request
|
||||
)
|
||||
except Exception as e:
|
||||
return error_response(
|
||||
error=f'Failed to load site definition: {str(e)}',
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
request=request
|
||||
)
|
||||
|
||||
# Fallback: find highest version number
|
||||
version_dirs = [d for d in site_dir.iterdir() if d.is_dir() and d.name.startswith('v')]
|
||||
if not version_dirs:
|
||||
return error_response(
|
||||
f'No deployed versions found for site {site_id}',
|
||||
status.HTTP_404_NOT_FOUND,
|
||||
request
|
||||
)
|
||||
|
||||
# Sort by version number (extract number from 'v1', 'v2', etc.)
|
||||
try:
|
||||
version_dirs.sort(key=lambda d: int(d.name[1:]) if d.name[1:].isdigit() else 0, reverse=True)
|
||||
latest_version_dir = version_dirs[0]
|
||||
site_json_path = latest_version_dir / 'site.json'
|
||||
|
||||
if site_json_path.exists():
|
||||
with open(site_json_path, 'r', encoding='utf-8') as f:
|
||||
definition = json.load(f)
|
||||
return Response(definition, status=status.HTTP_200_OK)
|
||||
except Exception as e:
|
||||
return error_response(
|
||||
f'Failed to load site definition: {str(e)}',
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
request
|
||||
error=f'Site definition not found for site {site_id}',
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
request=request
|
||||
)
|
||||
except Exception as e:
|
||||
# Catch any unhandled exceptions
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Unhandled exception in SiteDefinitionView: {str(e)}', exc_info=True)
|
||||
return error_response(
|
||||
error=f'Internal server error: {str(e)}',
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
request=request
|
||||
)
|
||||
|
||||
return error_response(
|
||||
f'Site definition not found for site {site_id}',
|
||||
status.HTTP_404_NOT_FOUND,
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@@ -39,6 +39,47 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
|
||||
permission_classes = [IsAuthenticatedAndActive, IsEditorOrAbove]
|
||||
throttle_scope = 'site_builder'
|
||||
throttle_classes = [DebugScopedRateThrottle]
|
||||
|
||||
def get_permissions(self):
|
||||
"""
|
||||
Allow public read access for list requests with site filter (used by Sites Renderer fallback).
|
||||
This allows the Sites Renderer to load blueprint data for deployed sites without authentication.
|
||||
"""
|
||||
# Allow public access for list requests with site filter (used by Sites Renderer)
|
||||
if self.action == 'list' and self.request.query_params.get('site'):
|
||||
from rest_framework.permissions import AllowAny
|
||||
return [AllowAny()]
|
||||
# Otherwise use default permissions
|
||||
return super().get_permissions()
|
||||
|
||||
def get_throttles(self):
|
||||
"""
|
||||
Bypass throttling for public list requests with site filter (used by Sites Renderer).
|
||||
"""
|
||||
# Bypass throttling for public requests (no auth) with site filter
|
||||
if self.action == 'list' and self.request.query_params.get('site'):
|
||||
if not self.request.user or not self.request.user.is_authenticated:
|
||||
return [] # No throttling for public blueprint access
|
||||
return super().get_throttles()
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Override to allow public access when filtering by site_id.
|
||||
"""
|
||||
# If this is a public request (no auth) with site filter, bypass base class filtering
|
||||
# and return deployed blueprints for that site
|
||||
if not self.request.user or not self.request.user.is_authenticated:
|
||||
site_id = self.request.query_params.get('site')
|
||||
if site_id:
|
||||
# Return queryset directly from model (bypassing base class account/site filtering)
|
||||
from igny8_core.business.site_building.models import SiteBlueprint
|
||||
return SiteBlueprint.objects.filter(
|
||||
site_id=site_id,
|
||||
status='deployed'
|
||||
).prefetch_related('pages').order_by('-version')
|
||||
|
||||
# For authenticated users, use base class filtering
|
||||
return super().get_queryset()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
from igny8_core.auth.models import Site, Sector
|
||||
|
||||
Reference in New Issue
Block a user