mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 13:31:08 -05:00
feat: Enhance Park Detail Endpoint with Media URL Service Integration
- Updated ParkDetailOutputSerializer to utilize MediaURLService for generating Cloudflare URLs and friendly URLs for park photos. - Added support for multiple lookup methods (ID and slug) in the park detail endpoint. - Improved documentation for the park detail endpoint, including request properties and response structure. - Created MediaURLService for generating SEO-friendly URLs and handling Cloudflare image URLs. - Comprehensive updates to frontend documentation to reflect new endpoint capabilities and usage examples. - Added detailed park detail endpoint documentation, including request and response structures, field descriptions, and usage examples.
This commit is contained in:
149
backend/apps/core/services/media_url_service.py
Normal file
149
backend/apps/core/services/media_url_service.py
Normal file
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
Media URL service for generating friendly URLs.
|
||||
|
||||
This service provides utilities for generating SEO-friendly URLs for media files
|
||||
while maintaining compatibility with Cloudflare Images.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Optional, Dict, Any
|
||||
from django.utils.text import slugify
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class MediaURLService:
|
||||
"""Service for generating and parsing friendly media URLs."""
|
||||
|
||||
@staticmethod
|
||||
def generate_friendly_filename(caption: str, photo_id: int, extension: str = "jpg") -> str:
|
||||
"""
|
||||
Generate a friendly filename from photo caption and ID.
|
||||
|
||||
Args:
|
||||
caption: Photo caption
|
||||
photo_id: Photo database ID
|
||||
extension: File extension (default: jpg)
|
||||
|
||||
Returns:
|
||||
Friendly filename like "beautiful-park-entrance-123.jpg"
|
||||
"""
|
||||
if caption:
|
||||
# Clean and slugify the caption
|
||||
slug = slugify(caption)
|
||||
# Limit length to avoid overly long URLs
|
||||
if len(slug) > 50:
|
||||
slug = slug[:50].rsplit('-', 1)[0] # Cut at word boundary
|
||||
return f"{slug}-{photo_id}.{extension}"
|
||||
else:
|
||||
return f"photo-{photo_id}.{extension}"
|
||||
|
||||
@staticmethod
|
||||
def generate_park_photo_url(park_slug: str, caption: str, photo_id: int, variant: str = "public") -> str:
|
||||
"""
|
||||
Generate a friendly URL for a park photo.
|
||||
|
||||
Args:
|
||||
park_slug: Park slug
|
||||
caption: Photo caption
|
||||
photo_id: Photo database ID
|
||||
variant: Image variant (public, thumbnail, medium, large)
|
||||
|
||||
Returns:
|
||||
Friendly URL like "/parks/cedar-point/photos/beautiful-entrance-123.jpg"
|
||||
"""
|
||||
filename = MediaURLService.generate_friendly_filename(caption, photo_id)
|
||||
|
||||
# Add variant to filename if not public
|
||||
if variant != "public":
|
||||
name, ext = filename.rsplit('.', 1)
|
||||
filename = f"{name}-{variant}.{ext}"
|
||||
|
||||
return f"/parks/{park_slug}/photos/{filename}"
|
||||
|
||||
@staticmethod
|
||||
def generate_ride_photo_url(park_slug: str, ride_slug: str, caption: str, photo_id: int, variant: str = "public") -> str:
|
||||
"""
|
||||
Generate a friendly URL for a ride photo.
|
||||
|
||||
Args:
|
||||
park_slug: Park slug
|
||||
ride_slug: Ride slug
|
||||
caption: Photo caption
|
||||
photo_id: Photo database ID
|
||||
variant: Image variant
|
||||
|
||||
Returns:
|
||||
Friendly URL like "/parks/cedar-point/rides/millennium-force/photos/first-drop-456.jpg"
|
||||
"""
|
||||
filename = MediaURLService.generate_friendly_filename(caption, photo_id)
|
||||
|
||||
if variant != "public":
|
||||
name, ext = filename.rsplit('.', 1)
|
||||
filename = f"{name}-{variant}.{ext}"
|
||||
|
||||
return f"/parks/{park_slug}/rides/{ride_slug}/photos/{filename}"
|
||||
|
||||
@staticmethod
|
||||
def parse_photo_filename(filename: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Parse a friendly filename to extract photo ID and variant.
|
||||
|
||||
Args:
|
||||
filename: Filename like "beautiful-entrance-123-thumbnail.jpg"
|
||||
|
||||
Returns:
|
||||
Dict with photo_id and variant, or None if parsing fails
|
||||
"""
|
||||
# Remove extension
|
||||
name = filename.rsplit('.', 1)[0]
|
||||
|
||||
# Check for variant suffix
|
||||
variant = "public"
|
||||
variant_patterns = ["thumbnail", "medium", "large"]
|
||||
|
||||
for v in variant_patterns:
|
||||
if name.endswith(f"-{v}"):
|
||||
variant = v
|
||||
name = name[:-len(f"-{v}")]
|
||||
break
|
||||
|
||||
# Extract photo ID (should be the last number)
|
||||
match = re.search(r'-(\d+)$', name)
|
||||
if match:
|
||||
photo_id = int(match.group(1))
|
||||
return {
|
||||
"photo_id": photo_id,
|
||||
"variant": variant
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_cloudflare_url_with_fallback(cloudflare_image, variant: str = "public") -> Optional[str]:
|
||||
"""
|
||||
Get Cloudflare URL with fallback handling.
|
||||
|
||||
Args:
|
||||
cloudflare_image: CloudflareImage instance
|
||||
variant: Desired variant
|
||||
|
||||
Returns:
|
||||
Cloudflare URL or None
|
||||
"""
|
||||
if not cloudflare_image:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Try the specific variant first
|
||||
url = cloudflare_image.get_url(variant)
|
||||
if url:
|
||||
return url
|
||||
|
||||
# Fallback to public URL
|
||||
if variant != "public":
|
||||
return cloudflare_image.public_url
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user