Skip to main content

Overview

Tesis Rutas uses Cloudinary for storing and managing all tourism-related media files including destination images, videos, and other multimedia content. This guide covers account setup, configuration, and best practices.

Prerequisites

  • A Cloudinary account (free tier available)
  • Python 3.8+ environment
  • Access to your project’s configuration files

Configuration Setup

1

Create Cloudinary Account

  1. Sign up at cloudinary.com
  2. Navigate to your Dashboard
  3. Copy your credentials:
    • Cloud Name
    • API Key
    • API Secret
2

Configure Environment Variables

Add your Cloudinary credentials to your environment configuration:
{
  "CLOUDINARY_CLOUD_NAME": "your_cloud_name",
  "CLOUDINARY_API_KEY": "your_api_key",
  "CLOUDINARY_API_SECRET": "your_api_secret"
}
The system automatically loads from local_config.json for development or .env for production environments.
3

Initialize Cloudinary Configuration

The configuration is automatically loaded via src/infrastructure/services/cloudinary_config.py:
src/infrastructure/services/cloudinary_config.py
import cloudinary
from src.config.settings import get_settings
from cloudinary import uploader

settings = get_settings()

cloudinary.config(
    cloud_name=settings.cloudinary_cloud_name,
    api_key=settings.cloudinary_api_key,
    api_secret=settings.cloudinary_api_secret,
    secure=True
)
The secure=True parameter ensures all uploads use HTTPS.

Folder Structure

Tesis Rutas organizes media by destination:
cloudinary_root/
└── destinations/
    ├── {destino_id_1}/
    │   ├── image1.jpg
    │   ├── image2.png
    │   └── video1.mp4
    └── {destino_id_2}/
        └── image1.jpg
Each destination has its own folder identified by the destination ID. Never upload files to the root directory.

Upload Implementation

Single File Upload

The platform supports uploading images and videos with automatic resource type detection:
import cloudinary.uploader

# Upload with automatic resource type detection
result = cloudinary.uploader.upload(
    file_bytes,
    folder=f"destinations/{destino_id}",
    resource_type="auto"
)

# Access upload information
url = result.get("secure_url")
public_id = result.get("public_id")
file_format = result.get("format")
size_bytes = result.get("bytes")

Multiple File Upload (Async)

For better performance, the platform uploads multiple files asynchronously:
src/infrastructure/api/routers/destinos_router.py
import asyncio
import cloudinary.uploader

async def upload_async(file: UploadFile):
    file_bytes = await file.read()
    return await asyncio.to_thread(
        cloudinary.uploader.upload,
        file_bytes,
        folder=f"destinations/{id}",
        resource_type="auto"
    )

resultados_cloudinary = await asyncio.gather(*[
    upload_async(file) for file in files
])

Upload Limits

The platform enforces the following limits:
Limit TypeValueDescription
Per Request1-3 filesMaximum files per upload request
Per Destination10 filesTotal multimedia files per destination
Resource Typesimage, videoSupported media types

Validation Example

src/infrastructure/api/routers/destinos_router.py
# Validate request limit
if len(files) == 0 or len(files) > 3:
    raise HTTPException(
        status_code=400,
        detail="Debes subir entre 1 y 3 archivos por solicitud."
    )

# Validate destination limit
total_actual = len(multimedia_actual)
if total_actual >= 10:
    raise HTTPException(
        status_code=400,
        detail="El destino ya tiene el máximo permitido de 10 archivos multimedia."
    )

if total_actual + len(files) > 10:
    raise HTTPException(
        status_code=400,
        detail=f"Solo puedes subir {10 - total_actual} archivos adicionales."
    )

Deleting Media

Delete Individual File

cloudinary.uploader.destroy(
    public_id,
    resource_type="image"  # or "video"
)

Delete Entire Destination Folder

When deleting a destination, remove its entire Cloudinary folder:
src/infrastructure/api/routers/destinos_router.py
ruta_carpeta = f"destinations/{destino_id}"

try:
    cloudinary.api.delete_folder(ruta_carpeta)
except Exception as e:
    # Handle error appropriately
    return {
        "message": "Error al borrar la carpeta en Cloudinary.",
        "cloudinary_error": str(e)
    }
Always delete all multimedia files from a destination before deleting the destination itself. The API enforces this validation.

Error Handling

Upload Rollback

If database insertion fails after upload, the platform automatically removes uploaded files:
src/infrastructure/api/routers/destinos_router.py
public_ids_subidos = [r.get("public_id") for r in resultados_cloudinary]

try:
    # Save to database
    await use_case.execute(...)
except Exception as e:
    # Rollback: delete uploaded files
    for pid in public_ids_subidos:
        try:
            cloudinary.uploader.destroy(pid)
        except:
            pass
    
    raise HTTPException(
        status_code=500,
        detail=f"Error al guardar multimedia, las subidas fueron revertidas."
    )

Best Practices

Always use the folder structure destinations/{destino_id} to keep media organized and make cleanup easier.
Let Cloudinary automatically detect whether the file is an image or video instead of manually specifying the type.
Save key information in your database:
  • secure_url: HTTPS URL to access the file
  • public_id: Unique identifier for deletion
  • format: File extension (jpg, png, mp4, etc.)
  • bytes: File size for tracking storage usage
Always implement rollback mechanisms to delete Cloudinary files if database operations fail.
Validate upload limits both per-request and per-destination to prevent abuse and manage storage costs.

Testing Your Setup

Verify your Cloudinary configuration:
import cloudinary
from src.config.settings import get_settings

settings = get_settings()

print(f"Cloud Name: {settings.cloudinary_cloud_name}")
print(f"API Key configured: {bool(settings.cloudinary_api_key)}")
print(f"API Secret configured: {bool(settings.cloudinary_api_secret)}")

# Test upload (optional)
result = cloudinary.uploader.upload(
    "test_image.jpg",
    folder="test",
    resource_type="auto"
)
print(f"Test upload successful: {result.get('secure_url')}")

Next Steps

API Reference

Explore the multimedia upload endpoints

Maps Integration

Display destinations on interactive maps

Build docs developers (and LLMs) love