feat(rides): populate slugs for existing RideModel records and ensure uniqueness

- Added migration 0011 to populate unique slugs for existing RideModel records based on manufacturer and model names.
- Implemented logic to ensure slug uniqueness during population.
- Added reverse migration to clear slugs if needed.

feat(rides): enforce unique slugs for RideModel

- Created migration 0012 to alter the slug field in RideModel to be unique.
- Updated the slug field to include help text and a maximum length of 255 characters.

docs: integrate Cloudflare Images into rides and parks models

- Updated RidePhoto and ParkPhoto models to use CloudflareImagesField for image storage.
- Enhanced API serializers for rides and parks to support Cloudflare Images, including new fields for image URLs and variants.
- Provided comprehensive OpenAPI schema metadata for new fields.
- Documented database migrations for the integration.
- Detailed configuration settings for Cloudflare Images.
- Updated API response formats to include Cloudflare Images URLs and variants.
- Added examples for uploading photos via API and outlined testing procedures.
This commit is contained in:
pacnpal
2025-08-28 15:12:39 -04:00
parent 715e284b3e
commit 67db0aa46e
34 changed files with 6002 additions and 894 deletions

View File

@@ -6,12 +6,44 @@ Enhanced from rogue implementation to maintain full feature parity.
"""
from rest_framework import serializers
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.utils import extend_schema_field, extend_schema_serializer, OpenApiExample
from apps.parks.models import Park, ParkPhoto
@extend_schema_serializer(
examples=[
OpenApiExample(
name='Park Photo with Cloudflare Images',
summary='Complete park photo response',
description='Example response showing all fields including Cloudflare Images URLs and variants',
value={
'id': 456,
'image': 'https://imagedelivery.net/account-hash/def456ghi789/public',
'image_url': 'https://imagedelivery.net/account-hash/def456ghi789/public',
'image_variants': {
'thumbnail': 'https://imagedelivery.net/account-hash/def456ghi789/thumbnail',
'medium': 'https://imagedelivery.net/account-hash/def456ghi789/medium',
'large': 'https://imagedelivery.net/account-hash/def456ghi789/large',
'public': 'https://imagedelivery.net/account-hash/def456ghi789/public'
},
'caption': 'Beautiful park entrance',
'alt_text': 'Main entrance gate with decorative archway',
'is_primary': True,
'is_approved': True,
'created_at': '2023-01-01T12:00:00Z',
'updated_at': '2023-01-01T12:00:00Z',
'date_taken': '2023-01-01T11:00:00Z',
'uploaded_by_username': 'parkfan456',
'file_size': 1536000,
'dimensions': [1600, 900],
'park_slug': 'cedar-point',
'park_name': 'Cedar Point'
}
)
]
)
class ParkPhotoOutputSerializer(serializers.ModelSerializer):
"""Enhanced output serializer for park photos with rich field structure."""
"""Enhanced output serializer for park photos with Cloudflare Images support."""
uploaded_by_username = serializers.CharField(
source="uploaded_by.username", read_only=True
@@ -19,6 +51,8 @@ class ParkPhotoOutputSerializer(serializers.ModelSerializer):
file_size = serializers.SerializerMethodField()
dimensions = serializers.SerializerMethodField()
image_url = serializers.SerializerMethodField()
image_variants = serializers.SerializerMethodField()
@extend_schema_field(
serializers.IntegerField(allow_null=True, help_text="File size in bytes")
@@ -40,6 +74,38 @@ class ParkPhotoOutputSerializer(serializers.ModelSerializer):
"""Get image dimensions as [width, height]."""
return obj.dimensions
@extend_schema_field(
serializers.URLField(
help_text="Full URL to the Cloudflare Images asset",
allow_null=True
)
)
def get_image_url(self, obj):
"""Get the full Cloudflare Images URL."""
if obj.image:
return obj.image.url
return None
@extend_schema_field(
serializers.DictField(
child=serializers.URLField(),
help_text="Available Cloudflare Images variants with their URLs"
)
)
def get_image_variants(self, obj):
"""Get available image variants from Cloudflare Images."""
if not obj.image:
return {}
# Common variants for park photos
variants = {
'thumbnail': f"{obj.image.url}/thumbnail",
'medium': f"{obj.image.url}/medium",
'large': f"{obj.image.url}/large",
'public': f"{obj.image.url}/public"
}
return variants
park_slug = serializers.CharField(source="park.slug", read_only=True)
park_name = serializers.CharField(source="park.name", read_only=True)
@@ -48,6 +114,8 @@ class ParkPhotoOutputSerializer(serializers.ModelSerializer):
fields = [
"id",
"image",
"image_url",
"image_variants",
"caption",
"alt_text",
"is_primary",
@@ -63,6 +131,8 @@ class ParkPhotoOutputSerializer(serializers.ModelSerializer):
]
read_only_fields = [
"id",
"image_url",
"image_variants",
"created_at",
"updated_at",
"uploaded_by_username",