mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 06:11:07 -05:00
@@ -1,574 +0,0 @@
|
||||
# Cloudflare Images Integration
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the complete integration of django-cloudflare-images into the ThrillWiki project for both rides and parks models, including full API schema metadata support.
|
||||
|
||||
## Implementation Summary
|
||||
|
||||
### 1. Models Updated
|
||||
|
||||
#### Rides Models (`backend/apps/rides/models/media.py`)
|
||||
- **RidePhoto.image**: Changed from `models.ImageField` to `CloudflareImagesField(variant="public")`
|
||||
- Added proper Meta class inheritance from `TrackedModel.Meta`
|
||||
- Maintains all existing functionality while leveraging Cloudflare Images
|
||||
|
||||
#### Parks Models (`backend/apps/parks/models/media.py`)
|
||||
- **ParkPhoto.image**: Changed from `models.ImageField` to `CloudflareImagesField(variant="public")`
|
||||
- Added proper Meta class inheritance from `TrackedModel.Meta`
|
||||
- Maintains all existing functionality while leveraging Cloudflare Images
|
||||
|
||||
### 2. API Serializers Enhanced
|
||||
|
||||
#### Rides API (`backend/apps/api/v1/rides/serializers.py`)
|
||||
- **RidePhotoOutputSerializer**: Enhanced with Cloudflare Images support
|
||||
- Added `image_url` field: Full URL to the Cloudflare Images asset
|
||||
- Added `image_variants` field: Dictionary of available image variants with URLs
|
||||
- Proper DRF Spectacular schema decorations with examples
|
||||
- Maintains backward compatibility
|
||||
|
||||
#### Parks API (`backend/apps/api/v1/parks/serializers.py`)
|
||||
- **ParkPhotoOutputSerializer**: Enhanced with Cloudflare Images support
|
||||
- Added `image_url` field: Full URL to the Cloudflare Images asset
|
||||
- Added `image_variants` field: Dictionary of available image variants with URLs
|
||||
- Proper DRF Spectacular schema decorations with examples
|
||||
- Maintains backward compatibility
|
||||
|
||||
### 3. Schema Metadata
|
||||
|
||||
Both serializers include comprehensive OpenAPI schema metadata:
|
||||
|
||||
- **Field Documentation**: All new fields have detailed help text and type information
|
||||
- **Examples**: Complete example responses showing Cloudflare Images URLs and variants
|
||||
- **Variants**: Documented image variants (thumbnail, medium, large, public) with descriptions
|
||||
|
||||
### 4. Database Migrations
|
||||
|
||||
- **rides.0008_cloudflare_images_integration**: Updates RidePhoto.image field
|
||||
- **parks.0009_cloudflare_images_integration**: Updates ParkPhoto.image field
|
||||
- Migrations applied successfully with no data loss
|
||||
|
||||
## Configuration
|
||||
|
||||
The project already has Cloudflare Images configured in `backend/config/django/base.py`:
|
||||
|
||||
```python
|
||||
# Cloudflare Images Settings
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "cloudflare_images.storage.CloudflareImagesStorage",
|
||||
},
|
||||
# ... other storage configs
|
||||
}
|
||||
|
||||
CLOUDFLARE_IMAGES_ACCOUNT_ID = config("CLOUDFLARE_IMAGES_ACCOUNT_ID")
|
||||
CLOUDFLARE_IMAGES_API_TOKEN = config("CLOUDFLARE_IMAGES_API_TOKEN")
|
||||
CLOUDFLARE_IMAGES_ACCOUNT_HASH = config("CLOUDFLARE_IMAGES_ACCOUNT_HASH")
|
||||
CLOUDFLARE_IMAGES_DOMAIN = config("CLOUDFLARE_IMAGES_DOMAIN", default="imagedelivery.net")
|
||||
```
|
||||
|
||||
## API Response Format
|
||||
|
||||
### Enhanced Photo Response
|
||||
|
||||
Both ride and park photo endpoints now return:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 123,
|
||||
"image": "https://imagedelivery.net/account-hash/image-id/public",
|
||||
"image_url": "https://imagedelivery.net/account-hash/image-id/public",
|
||||
"image_variants": {
|
||||
"thumbnail": "https://imagedelivery.net/account-hash/image-id/thumbnail",
|
||||
"medium": "https://imagedelivery.net/account-hash/image-id/medium",
|
||||
"large": "https://imagedelivery.net/account-hash/image-id/large",
|
||||
"public": "https://imagedelivery.net/account-hash/image-id/public"
|
||||
},
|
||||
"caption": "Photo caption",
|
||||
"alt_text": "Alt text for accessibility",
|
||||
"is_primary": true,
|
||||
"is_approved": true,
|
||||
"photo_type": "exterior", // rides only
|
||||
"created_at": "2023-01-01T12:00:00Z",
|
||||
"updated_at": "2023-01-01T12:00:00Z",
|
||||
"date_taken": "2023-01-01T10:00:00Z",
|
||||
"uploaded_by_username": "photographer123",
|
||||
"file_size": 2048576,
|
||||
"dimensions": [1920, 1080],
|
||||
"ride_slug": "steel-vengeance", // rides only
|
||||
"ride_name": "Steel Vengeance", // rides only
|
||||
"park_slug": "cedar-point",
|
||||
"park_name": "Cedar Point"
|
||||
}
|
||||
```
|
||||
|
||||
## Image Variants
|
||||
|
||||
The integration provides these standard variants:
|
||||
|
||||
- **thumbnail**: 150x150px - Perfect for list views and previews
|
||||
- **medium**: 500x500px - Good for modal previews and medium displays
|
||||
- **large**: 1200x1200px - High quality for detailed views
|
||||
- **public**: Original size - Full resolution image
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Performance**: Cloudflare's global CDN ensures fast image delivery
|
||||
2. **Optimization**: Automatic image optimization and format conversion
|
||||
3. **Variants**: Multiple image sizes generated automatically
|
||||
4. **Scalability**: No local storage requirements
|
||||
5. **API Documentation**: Complete OpenAPI schema with examples
|
||||
6. **Backward Compatibility**: Existing API consumers continue to work
|
||||
7. **Entity Validation**: Photos are always associated with valid rides or parks
|
||||
8. **Data Integrity**: Prevents orphaned photos without parent entities
|
||||
9. **Automatic Photo Inclusion**: Photos are automatically included when displaying rides and parks
|
||||
10. **Primary Photo Support**: Easy access to the main photo for each entity
|
||||
|
||||
## Automatic Photo Integration
|
||||
|
||||
### Ride Detail Responses
|
||||
|
||||
When fetching ride details via `GET /api/v1/rides/{id}/`, the response automatically includes:
|
||||
|
||||
- **photos**: Array of up to 10 approved photos with full Cloudflare Images variants
|
||||
- **primary_photo**: The designated primary photo for the ride (if available)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Steel Vengeance",
|
||||
"slug": "steel-vengeance",
|
||||
"photos": [
|
||||
{
|
||||
"id": 123,
|
||||
"image_url": "https://imagedelivery.net/account-hash/abc123def456/public",
|
||||
"image_variants": {
|
||||
"thumbnail": "https://imagedelivery.net/account-hash/abc123def456/thumbnail",
|
||||
"medium": "https://imagedelivery.net/account-hash/abc123def456/medium",
|
||||
"large": "https://imagedelivery.net/account-hash/abc123def456/large",
|
||||
"public": "https://imagedelivery.net/account-hash/abc123def456/public"
|
||||
},
|
||||
"caption": "Amazing roller coaster photo",
|
||||
"alt_text": "Steel roller coaster with multiple inversions",
|
||||
"is_primary": true,
|
||||
"photo_type": "exterior"
|
||||
}
|
||||
],
|
||||
"primary_photo": {
|
||||
"id": 123,
|
||||
"image_url": "https://imagedelivery.net/account-hash/abc123def456/public",
|
||||
"image_variants": {
|
||||
"thumbnail": "https://imagedelivery.net/account-hash/abc123def456/thumbnail",
|
||||
"medium": "https://imagedelivery.net/account-hash/abc123def456/medium",
|
||||
"large": "https://imagedelivery.net/account-hash/abc123def456/large",
|
||||
"public": "https://imagedelivery.net/account-hash/abc123def456/public"
|
||||
},
|
||||
"caption": "Amazing roller coaster photo",
|
||||
"alt_text": "Steel roller coaster with multiple inversions",
|
||||
"photo_type": "exterior"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Park Detail Responses
|
||||
|
||||
When fetching park details via `GET /api/v1/parks/{id}/`, the response automatically includes:
|
||||
|
||||
- **photos**: Array of up to 10 approved photos with full Cloudflare Images variants
|
||||
- **primary_photo**: The designated primary photo for the park (if available)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Cedar Point",
|
||||
"slug": "cedar-point",
|
||||
"photos": [
|
||||
{
|
||||
"id": 456,
|
||||
"image_url": "https://imagedelivery.net/account-hash/def789ghi012/public",
|
||||
"image_variants": {
|
||||
"thumbnail": "https://imagedelivery.net/account-hash/def789ghi012/thumbnail",
|
||||
"medium": "https://imagedelivery.net/account-hash/def789ghi012/medium",
|
||||
"large": "https://imagedelivery.net/account-hash/def789ghi012/large",
|
||||
"public": "https://imagedelivery.net/account-hash/def789ghi012/public"
|
||||
},
|
||||
"caption": "Beautiful park entrance",
|
||||
"alt_text": "Cedar Point main entrance with flags",
|
||||
"is_primary": true
|
||||
}
|
||||
],
|
||||
"primary_photo": {
|
||||
"id": 456,
|
||||
"image_url": "https://imagedelivery.net/account-hash/def789ghi012/public",
|
||||
"image_variants": {
|
||||
"thumbnail": "https://imagedelivery.net/account-hash/def789ghi012/thumbnail",
|
||||
"medium": "https://imagedelivery.net/account-hash/def789ghi012/medium",
|
||||
"large": "https://imagedelivery.net/account-hash/def789ghi012/large",
|
||||
"public": "https://imagedelivery.net/account-hash/def789ghi012/public"
|
||||
},
|
||||
"caption": "Beautiful park entrance",
|
||||
"alt_text": "Cedar Point main entrance with flags"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Photo Filtering
|
||||
|
||||
- Only **approved** photos (`is_approved=True`) are included in entity responses
|
||||
- Photos are ordered by **primary status first**, then by **creation date** (newest first)
|
||||
- Limited to **10 photos maximum** per entity to maintain response performance
|
||||
- **Primary photo** is provided separately for easy access to the main image
|
||||
|
||||
## Testing
|
||||
|
||||
The implementation has been verified:
|
||||
- ✅ Models successfully use CloudflareImagesField
|
||||
- ✅ Migrations applied without issues
|
||||
- ✅ Serializers import and function correctly
|
||||
- ✅ Schema metadata properly configured
|
||||
- ✅ Photos automatically included in ride and park detail responses
|
||||
- ✅ Primary photo selection working correctly
|
||||
|
||||
## Upload Examples
|
||||
|
||||
### 1. Upload Ride Photo via API
|
||||
|
||||
**Endpoint:** `POST /api/v1/rides/{ride_id}/photos/`
|
||||
|
||||
**Requirements:**
|
||||
- Valid JWT authentication token
|
||||
- Existing ride with the specified `ride_id`
|
||||
- Image file in supported format (JPEG, PNG, WebP, etc.)
|
||||
|
||||
**Headers:**
|
||||
```bash
|
||||
Authorization: Bearer <your_jwt_token>
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
**cURL Example:**
|
||||
```bash
|
||||
curl -X POST "https://your-domain.com/api/v1/rides/123/photos/" \
|
||||
-H "Authorization: Bearer your_jwt_token_here" \
|
||||
-F "image=@/path/to/your/photo.jpg" \
|
||||
-F "caption=Amazing steel coaster shot" \
|
||||
-F "alt_text=Steel Vengeance coaster with riders" \
|
||||
-F "photo_type=exterior" \
|
||||
-F "is_primary=false"
|
||||
```
|
||||
|
||||
**Error Response (Non-existent Ride):**
|
||||
```json
|
||||
{
|
||||
"detail": "Ride not found"
|
||||
}
|
||||
```
|
||||
|
||||
**Python Example:**
|
||||
```python
|
||||
import requests
|
||||
|
||||
url = "https://your-domain.com/api/v1/rides/123/photos/"
|
||||
headers = {"Authorization": "Bearer your_jwt_token_here"}
|
||||
|
||||
with open("/path/to/your/photo.jpg", "rb") as image_file:
|
||||
files = {"image": image_file}
|
||||
data = {
|
||||
"caption": "Amazing steel coaster shot",
|
||||
"alt_text": "Steel Vengeance coaster with riders",
|
||||
"photo_type": "exterior",
|
||||
"is_primary": False
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, files=files, data=data)
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
**JavaScript Example:**
|
||||
```javascript
|
||||
const formData = new FormData();
|
||||
formData.append('image', fileInput.files[0]);
|
||||
formData.append('caption', 'Amazing steel coaster shot');
|
||||
formData.append('alt_text', 'Steel Vengeance coaster with riders');
|
||||
formData.append('photo_type', 'exterior');
|
||||
formData.append('is_primary', 'false');
|
||||
|
||||
fetch('/api/v1/rides/123/photos/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer your_jwt_token_here'
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => console.log(data));
|
||||
```
|
||||
|
||||
### 2. Upload Park Photo via API
|
||||
|
||||
**Endpoint:** `POST /api/v1/parks/{park_id}/photos/`
|
||||
|
||||
**Requirements:**
|
||||
- Valid JWT authentication token
|
||||
- Existing park with the specified `park_id`
|
||||
- Image file in supported format (JPEG, PNG, WebP, etc.)
|
||||
|
||||
**cURL Example:**
|
||||
```bash
|
||||
curl -X POST "https://your-domain.com/api/v1/parks/456/photos/" \
|
||||
-H "Authorization: Bearer your_jwt_token_here" \
|
||||
-F "image=@/path/to/park-entrance.jpg" \
|
||||
-F "caption=Beautiful park entrance" \
|
||||
-F "alt_text=Cedar Point main entrance with flags" \
|
||||
-F "is_primary=true"
|
||||
```
|
||||
|
||||
**Error Response (Non-existent Park):**
|
||||
```json
|
||||
{
|
||||
"detail": "Park not found"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Upload Response Format
|
||||
|
||||
Both endpoints return the same enhanced format with Cloudflare Images integration:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 789,
|
||||
"image": "https://imagedelivery.net/account-hash/image-id/public",
|
||||
"image_url": "https://imagedelivery.net/account-hash/image-id/public",
|
||||
"image_variants": {
|
||||
"thumbnail": "https://imagedelivery.net/account-hash/image-id/thumbnail",
|
||||
"medium": "https://imagedelivery.net/account-hash/image-id/medium",
|
||||
"large": "https://imagedelivery.net/account-hash/image-id/large",
|
||||
"public": "https://imagedelivery.net/account-hash/image-id/public"
|
||||
},
|
||||
"caption": "Amazing steel coaster shot",
|
||||
"alt_text": "Steel Vengeance coaster with riders",
|
||||
"is_primary": false,
|
||||
"is_approved": false,
|
||||
"photo_type": "exterior",
|
||||
"created_at": "2023-01-01T12:00:00Z",
|
||||
"updated_at": "2023-01-01T12:00:00Z",
|
||||
"date_taken": null,
|
||||
"uploaded_by_username": "photographer123",
|
||||
"file_size": 2048576,
|
||||
"dimensions": [1920, 1080],
|
||||
"ride_slug": "steel-vengeance",
|
||||
"ride_name": "Steel Vengeance",
|
||||
"park_slug": "cedar-point",
|
||||
"park_name": "Cedar Point"
|
||||
}
|
||||
```
|
||||
|
||||
## Cloudflare Images Transformations
|
||||
|
||||
### 1. Built-in Variants
|
||||
|
||||
The integration provides these pre-configured variants:
|
||||
|
||||
- **thumbnail** (150x150px): `https://imagedelivery.net/account-hash/image-id/thumbnail`
|
||||
- **medium** (500x500px): `https://imagedelivery.net/account-hash/image-id/medium`
|
||||
- **large** (1200x1200px): `https://imagedelivery.net/account-hash/image-id/large`
|
||||
- **public** (original): `https://imagedelivery.net/account-hash/image-id/public`
|
||||
|
||||
### 2. Custom Transformations
|
||||
|
||||
You can apply custom transformations by appending parameters to any variant URL:
|
||||
|
||||
#### Resize Examples:
|
||||
```
|
||||
# Resize to specific width (maintains aspect ratio)
|
||||
https://imagedelivery.net/account-hash/image-id/public/w=800
|
||||
|
||||
# Resize to specific height (maintains aspect ratio)
|
||||
https://imagedelivery.net/account-hash/image-id/public/h=600
|
||||
|
||||
# Resize to exact dimensions (may crop)
|
||||
https://imagedelivery.net/account-hash/image-id/public/w=800,h=600
|
||||
|
||||
# Resize with fit modes
|
||||
https://imagedelivery.net/account-hash/image-id/public/w=800,h=600,fit=cover
|
||||
https://imagedelivery.net/account-hash/image-id/public/w=800,h=600,fit=contain
|
||||
https://imagedelivery.net/account-hash/image-id/public/w=800,h=600,fit=crop
|
||||
```
|
||||
|
||||
#### Quality and Format:
|
||||
```
|
||||
# Adjust quality (1-100)
|
||||
https://imagedelivery.net/account-hash/image-id/public/quality=85
|
||||
|
||||
# Convert format
|
||||
https://imagedelivery.net/account-hash/image-id/public/format=webp
|
||||
https://imagedelivery.net/account-hash/image-id/public/format=avif
|
||||
|
||||
# Auto format (serves best format for browser)
|
||||
https://imagedelivery.net/account-hash/image-id/public/format=auto
|
||||
```
|
||||
|
||||
#### Advanced Transformations:
|
||||
```
|
||||
# Blur effect
|
||||
https://imagedelivery.net/account-hash/image-id/public/blur=5
|
||||
|
||||
# Sharpen
|
||||
https://imagedelivery.net/account-hash/image-id/public/sharpen=2
|
||||
|
||||
# Brightness adjustment (-100 to 100)
|
||||
https://imagedelivery.net/account-hash/image-id/public/brightness=20
|
||||
|
||||
# Contrast adjustment (-100 to 100)
|
||||
https://imagedelivery.net/account-hash/image-id/public/contrast=15
|
||||
|
||||
# Gamma adjustment (0.1 to 2.0)
|
||||
https://imagedelivery.net/account-hash/image-id/public/gamma=1.2
|
||||
|
||||
# Rotate (90, 180, 270 degrees)
|
||||
https://imagedelivery.net/account-hash/image-id/public/rotate=90
|
||||
```
|
||||
|
||||
#### Combining Transformations:
|
||||
```
|
||||
# Multiple transformations (comma-separated)
|
||||
https://imagedelivery.net/account-hash/image-id/public/w=800,h=600,fit=cover,quality=85,format=webp
|
||||
|
||||
# Responsive image for mobile
|
||||
https://imagedelivery.net/account-hash/image-id/public/w=400,quality=80,format=auto
|
||||
|
||||
# High-quality desktop version
|
||||
https://imagedelivery.net/account-hash/image-id/public/w=1200,quality=90,format=auto
|
||||
```
|
||||
|
||||
### 3. Creating Custom Variants
|
||||
|
||||
You can create custom variants in your Cloudflare Images dashboard for commonly used transformations:
|
||||
|
||||
1. Go to Cloudflare Images dashboard
|
||||
2. Navigate to "Variants" section
|
||||
3. Create new variant with desired transformations
|
||||
4. Use in your models:
|
||||
|
||||
```python
|
||||
# In your model
|
||||
class RidePhoto(TrackedModel):
|
||||
image = CloudflareImagesField(variant="hero_banner") # Custom variant
|
||||
```
|
||||
|
||||
### 4. Responsive Images Implementation
|
||||
|
||||
Use different variants for responsive design:
|
||||
|
||||
```html
|
||||
<!-- HTML with responsive variants -->
|
||||
<picture>
|
||||
<source media="(max-width: 480px)"
|
||||
srcset="https://imagedelivery.net/account-hash/image-id/thumbnail">
|
||||
<source media="(max-width: 768px)"
|
||||
srcset="https://imagedelivery.net/account-hash/image-id/medium">
|
||||
<source media="(max-width: 1200px)"
|
||||
srcset="https://imagedelivery.net/account-hash/image-id/large">
|
||||
<img src="https://imagedelivery.net/account-hash/image-id/public"
|
||||
alt="Ride photo">
|
||||
</picture>
|
||||
```
|
||||
|
||||
```css
|
||||
/* CSS with responsive variants */
|
||||
.ride-photo {
|
||||
background-image: url('https://imagedelivery.net/account-hash/image-id/thumbnail');
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.ride-photo {
|
||||
background-image: url('https://imagedelivery.net/account-hash/image-id/medium');
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.ride-photo {
|
||||
background-image: url('https://imagedelivery.net/account-hash/image-id/large');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Performance Optimization
|
||||
|
||||
**Best Practices:**
|
||||
- Use `format=auto` to serve optimal format (WebP, AVIF) based on browser support
|
||||
- Set appropriate quality levels (80-85 for photos, 90+ for graphics)
|
||||
- Use `fit=cover` for consistent aspect ratios in galleries
|
||||
- Implement lazy loading with smaller variants as placeholders
|
||||
|
||||
**Example Optimized URLs:**
|
||||
```
|
||||
# Gallery thumbnail (fast loading)
|
||||
https://imagedelivery.net/account-hash/image-id/thumbnail/quality=75,format=auto
|
||||
|
||||
# Modal preview (balanced quality/size)
|
||||
https://imagedelivery.net/account-hash/image-id/medium/quality=85,format=auto
|
||||
|
||||
# Full-size view (high quality)
|
||||
https://imagedelivery.net/account-hash/image-id/large/quality=90,format=auto
|
||||
```
|
||||
|
||||
## Testing and Verification
|
||||
|
||||
### 1. Verify Upload Functionality
|
||||
|
||||
```bash
|
||||
# Test ride photo upload (requires existing ride with ID 1)
|
||||
curl -X POST "http://localhost:8000/api/v1/rides/1/photos/" \
|
||||
-H "Authorization: Bearer your_test_token" \
|
||||
-F "image=@test_image.jpg" \
|
||||
-F "caption=Test upload"
|
||||
|
||||
# Test park photo upload (requires existing park with ID 1)
|
||||
curl -X POST "http://localhost:8000/api/v1/parks/1/photos/" \
|
||||
-H "Authorization: Bearer your_test_token" \
|
||||
-F "image=@test_image.jpg" \
|
||||
-F "caption=Test park upload"
|
||||
|
||||
# Test with non-existent entity (should return 400 error)
|
||||
curl -X POST "http://localhost:8000/api/v1/rides/99999/photos/" \
|
||||
-H "Authorization: Bearer your_test_token" \
|
||||
-F "image=@test_image.jpg" \
|
||||
-F "caption=Test upload"
|
||||
```
|
||||
|
||||
### 2. Verify Image Variants
|
||||
|
||||
```python
|
||||
# Django shell verification
|
||||
from apps.rides.models import RidePhoto
|
||||
|
||||
photo = RidePhoto.objects.first()
|
||||
print(f"Image URL: {photo.image.url}")
|
||||
print(f"Thumbnail: {photo.image.url.replace('/public', '/thumbnail')}")
|
||||
print(f"Medium: {photo.image.url.replace('/public', '/medium')}")
|
||||
print(f"Large: {photo.image.url.replace('/public', '/large')}")
|
||||
```
|
||||
|
||||
### 3. Test Transformations
|
||||
|
||||
Visit these URLs in your browser to verify transformations work:
|
||||
- Original: `https://imagedelivery.net/your-hash/image-id/public`
|
||||
- Resized: `https://imagedelivery.net/your-hash/image-id/public/w=400`
|
||||
- WebP: `https://imagedelivery.net/your-hash/image-id/public/format=webp`
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential future improvements:
|
||||
- Signed URLs for private images
|
||||
- Batch upload capabilities
|
||||
- Image analytics integration
|
||||
- Advanced AI-powered transformations
|
||||
- Custom watermarking
|
||||
- Automatic alt-text generation
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `django-cloudflare-images>=0.6.0` (already installed)
|
||||
- Proper environment variables configured
|
||||
- Cloudflare Images account setup
|
||||
@@ -1,169 +0,0 @@
|
||||
# Comprehensive Ride Filtering System Design
|
||||
|
||||
## Overview
|
||||
This document outlines the design for a robust ride filtering system with PostgreSQL full-text search capabilities, organized in logical filter categories with a beautiful Tailwind 4 UI.
|
||||
|
||||
## Filter Categories
|
||||
|
||||
### 1. Search & Text Filters
|
||||
- **Main Search Bar**: Full-text search across name, description, park name using PostgreSQL SearchVector
|
||||
- **Advanced Search**: Separate fields for ride name, park name, description
|
||||
- **Fuzzy Search**: TrigramSimilarity for typo-tolerant searching
|
||||
|
||||
### 2. Basic Ride Information
|
||||
- **Category**: Multi-select dropdown (Roller Coaster, Dark Ride, Flat Ride, Water Ride, Transport, Other)
|
||||
- **Status**: Multi-select dropdown (Operating, Temporarily Closed, SBNO, Closing, Permanently Closed, Under Construction, Demolished, Relocated)
|
||||
|
||||
### 3. Date Filters
|
||||
- **Opening Date Range**: Date picker for from/to dates
|
||||
- **Closing Date Range**: Date picker for from/to dates
|
||||
- **Status Since**: Date picker for from/to dates
|
||||
|
||||
### 4. Height & Safety Requirements
|
||||
- **Minimum Height**: Range slider (30-90 inches)
|
||||
- **Maximum Height**: Range slider (30-90 inches)
|
||||
- **Height Requirement Type**: No requirement, minimum only, maximum only, both
|
||||
|
||||
### 5. Performance Metrics
|
||||
- **Capacity per Hour**: Range slider (0-5000+ people)
|
||||
- **Ride Duration**: Range slider (0-600+ seconds)
|
||||
- **Average Rating**: Range slider (1-10 stars)
|
||||
|
||||
### 6. Location & Relationships
|
||||
- **Park**: Multi-select autocomplete dropdown
|
||||
- **Park Area**: Multi-select dropdown (filtered by selected parks)
|
||||
- **Manufacturer**: Multi-select autocomplete dropdown
|
||||
- **Designer**: Multi-select autocomplete dropdown
|
||||
- **Ride Model**: Multi-select autocomplete dropdown
|
||||
|
||||
### 7. Roller Coaster Specific (only shown when RC category selected)
|
||||
- **Height**: Range slider (0-500+ feet)
|
||||
- **Length**: Range slider (0-10000+ feet)
|
||||
- **Speed**: Range slider (0-150+ mph)
|
||||
- **Inversions**: Range slider (0-20+)
|
||||
- **Max Drop Height**: Range slider (0-500+ feet)
|
||||
- **Track Material**: Multi-select (Steel, Wood, Hybrid)
|
||||
- **Coaster Type**: Multi-select (Sit Down, Inverted, Flying, Stand Up, Wing, Dive, Family, Wild Mouse, Spinning, 4th Dimension, Other)
|
||||
- **Launch Type**: Multi-select (Chain Lift, LSM Launch, Hydraulic Launch, Gravity, Other)
|
||||
|
||||
### 8. Company Information (when manufacturer/designer filters are used)
|
||||
- **Company Founded**: Date range picker
|
||||
- **Company Roles**: Multi-select (Manufacturer, Designer, Operator, Property Owner)
|
||||
- **Rides Count**: Range slider (1-500+)
|
||||
- **Coasters Count**: Range slider (1-200+)
|
||||
|
||||
## Sorting Options
|
||||
- **Name** (A-Z, Z-A)
|
||||
- **Opening Date** (Newest, Oldest)
|
||||
- **Average Rating** (Highest, Lowest)
|
||||
- **Height** (Tallest, Shortest) - for roller coasters
|
||||
- **Speed** (Fastest, Slowest) - for roller coasters
|
||||
- **Capacity** (Highest, Lowest)
|
||||
- **Recently Updated** (using pghistory)
|
||||
- **Relevance** (for text searches using SearchRank)
|
||||
|
||||
## UI/UX Design Principles
|
||||
|
||||
### Layout Structure
|
||||
- **Desktop**: Sidebar filter panel (collapsible) + main content area
|
||||
- **Mobile**: Overlay filter modal with bottom sheet design
|
||||
- **Filter Organization**: Collapsible sections with clean headers and icons
|
||||
|
||||
### Tailwind 4 Design System
|
||||
- **Color Scheme**: Modern neutral palette with theme support
|
||||
- **Typography**: Clear hierarchy with proper contrast
|
||||
- **Spacing**: Consistent spacing scale (4, 8, 16, 24, 32px)
|
||||
- **Interactive Elements**: Smooth transitions and hover states
|
||||
- **Form Controls**: Custom-styled inputs, dropdowns, sliders
|
||||
|
||||
### Dark Mode Support
|
||||
- Full dark mode implementation using Tailwind's dark: variants
|
||||
- Theme toggle in header
|
||||
- Proper contrast ratios for accessibility
|
||||
- Dark-friendly color selections for data visualization
|
||||
|
||||
### Responsive Design
|
||||
- **Mobile First**: Progressive enhancement approach
|
||||
- **Breakpoints**: sm (640px), md (768px), lg (1024px), xl (1280px)
|
||||
- **Touch Friendly**: Larger touch targets on mobile
|
||||
- **Collapsible Filters**: Smart grouping on smaller screens
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### Backend Architecture
|
||||
1. **Search Service**: Django service layer for search logic
|
||||
2. **PostgreSQL Features**: SearchVector, SearchQuery, SearchRank, GIN indexes
|
||||
3. **Fuzzy Matching**: TrigramSimilarity for typo tolerance
|
||||
4. **Caching**: Redis for popular filter combinations
|
||||
5. **Pagination**: Efficient pagination with search ranking
|
||||
|
||||
### Frontend Features
|
||||
1. **HTMX Integration**: Real-time filtering without full page reloads
|
||||
2. **URL Persistence**: Filter state preserved in URL parameters
|
||||
3. **Autocomplete**: Intelligent suggestions for text fields
|
||||
4. **Filter Counts**: Show result counts for each filter option
|
||||
5. **Active Filters**: Visual indicators of applied filters
|
||||
6. **Reset Functionality**: Clear individual or all filters
|
||||
|
||||
### Performance Optimizations
|
||||
1. **Database Indexes**: GIN indexes on search vectors
|
||||
2. **Query Optimization**: Efficient JOIN strategies
|
||||
3. **Caching Layer**: Popular searches cached in Redis
|
||||
4. **Debounced Input**: Prevent excessive API calls
|
||||
5. **Lazy Loading**: Load expensive filter options on demand
|
||||
|
||||
## Accessibility Features
|
||||
- **Keyboard Navigation**: Full keyboard support for all controls
|
||||
- **Screen Readers**: Proper ARIA labels and descriptions
|
||||
- **Color Contrast**: WCAG AA compliance
|
||||
- **Focus Management**: Clear focus indicators
|
||||
- **Alternative Text**: Descriptive labels for all controls
|
||||
|
||||
## Search Algorithm Details
|
||||
|
||||
### PostgreSQL Full-Text Search
|
||||
```sql
|
||||
-- Search vector combining multiple fields with weights
|
||||
search_vector = (
|
||||
setweight(to_tsvector('english', name), 'A') ||
|
||||
setweight(to_tsvector('english', park.name), 'B') ||
|
||||
setweight(to_tsvector('english', description), 'C') ||
|
||||
setweight(to_tsvector('english', manufacturer.name), 'D')
|
||||
)
|
||||
|
||||
-- Search query with ranking
|
||||
SELECT *, ts_rank(search_vector, query) as rank
|
||||
FROM rides
|
||||
WHERE search_vector @@ query
|
||||
ORDER BY rank DESC, name
|
||||
```
|
||||
|
||||
### Trigram Similarity
|
||||
```sql
|
||||
-- Fuzzy matching for typos
|
||||
SELECT *
|
||||
FROM rides
|
||||
WHERE similarity(name, 'search_term') > 0.3
|
||||
ORDER BY similarity(name, 'search_term') DESC
|
||||
```
|
||||
|
||||
## Filter State Management
|
||||
- **URL Parameters**: All filter state stored in URL for shareability
|
||||
- **Local Storage**: Remember user preferences
|
||||
- **Session State**: Maintain state during browsing session
|
||||
- **Bookmark Support**: Full filter state can be bookmarked
|
||||
|
||||
## Testing Strategy
|
||||
1. **Unit Tests**: Individual filter components
|
||||
2. **Integration Tests**: Full filtering workflows
|
||||
3. **Performance Tests**: Large dataset filtering
|
||||
4. **Accessibility Tests**: Screen reader and keyboard testing
|
||||
5. **Mobile Tests**: Touch interface testing
|
||||
6. **Browser Tests**: Cross-browser compatibility
|
||||
|
||||
## Future Enhancements
|
||||
1. **Saved Filters**: User accounts can save filter combinations
|
||||
2. **Advanced Analytics**: Popular filter combinations tracking
|
||||
3. **Machine Learning**: Personalized filter suggestions
|
||||
4. **Export Features**: Export filtered results to CSV/PDF
|
||||
5. **Comparison Mode**: Side-by-side ride comparisons
|
||||
Reference in New Issue
Block a user