mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 07:51:13 -05:00
- Created a base email template (base.html) for consistent styling across all emails. - Added moderation approval email template (moderation_approved.html) to notify users of approved submissions. - Added moderation rejection email template (moderation_rejected.html) to inform users of required changes for their submissions. - Created password reset email template (password_reset.html) for users requesting to reset their passwords. - Developed a welcome email template (welcome.html) to greet new users and provide account details and tips for using ThrillWiki.
220 lines
6.8 KiB
Python
220 lines
6.8 KiB
Python
"""
|
|
Background tasks for media processing and management.
|
|
"""
|
|
|
|
import logging
|
|
from celery import shared_task
|
|
from django.utils import timezone
|
|
from datetime import timedelta
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
|
|
def process_uploaded_image(self, photo_id):
|
|
"""
|
|
Process an uploaded image asynchronously.
|
|
|
|
This task runs after a photo is uploaded to perform additional
|
|
processing like metadata extraction, validation, etc.
|
|
|
|
Args:
|
|
photo_id: ID of the Photo to process
|
|
|
|
Returns:
|
|
str: Processing result message
|
|
"""
|
|
from apps.media.models import Photo
|
|
|
|
try:
|
|
photo = Photo.objects.get(id=photo_id)
|
|
|
|
# Log processing start
|
|
logger.info(f"Processing photo {photo_id}: {photo.title}")
|
|
|
|
# Additional processing could include:
|
|
# - Generating additional thumbnails
|
|
# - Extracting EXIF data
|
|
# - Running image quality checks
|
|
# - Updating photo metadata
|
|
|
|
# For now, just log that processing is complete
|
|
logger.info(f"Photo {photo_id} processed successfully")
|
|
|
|
return f"Photo {photo_id} processed successfully"
|
|
|
|
except Photo.DoesNotExist:
|
|
logger.error(f"Photo {photo_id} not found")
|
|
raise
|
|
except Exception as exc:
|
|
logger.error(f"Error processing photo {photo_id}: {str(exc)}")
|
|
# Retry with exponential backoff
|
|
raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries))
|
|
|
|
|
|
@shared_task(bind=True, max_retries=2)
|
|
def cleanup_rejected_photos(self, days_old=30):
|
|
"""
|
|
Clean up photos that have been rejected for more than N days.
|
|
|
|
This task runs periodically (e.g., weekly) to remove old rejected
|
|
photos and free up storage space.
|
|
|
|
Args:
|
|
days_old: Number of days after rejection to delete (default: 30)
|
|
|
|
Returns:
|
|
dict: Cleanup statistics
|
|
"""
|
|
from apps.media.models import Photo
|
|
from apps.media.services import PhotoService
|
|
|
|
try:
|
|
cutoff_date = timezone.now() - timedelta(days=days_old)
|
|
|
|
# Find rejected photos older than cutoff
|
|
old_rejected = Photo.objects.filter(
|
|
moderation_status='rejected',
|
|
moderated_at__lt=cutoff_date
|
|
)
|
|
|
|
count = old_rejected.count()
|
|
logger.info(f"Found {count} rejected photos to cleanup")
|
|
|
|
# Delete each photo
|
|
photo_service = PhotoService()
|
|
deleted_count = 0
|
|
|
|
for photo in old_rejected:
|
|
try:
|
|
photo_service.delete_photo(photo, delete_from_cloudflare=True)
|
|
deleted_count += 1
|
|
except Exception as e:
|
|
logger.error(f"Failed to delete photo {photo.id}: {str(e)}")
|
|
continue
|
|
|
|
result = {
|
|
'found': count,
|
|
'deleted': deleted_count,
|
|
'failed': count - deleted_count,
|
|
'cutoff_date': cutoff_date.isoformat()
|
|
}
|
|
|
|
logger.info(f"Cleanup complete: {result}")
|
|
return result
|
|
|
|
except Exception as exc:
|
|
logger.error(f"Error during photo cleanup: {str(exc)}")
|
|
raise self.retry(exc=exc, countdown=300) # Retry after 5 minutes
|
|
|
|
|
|
@shared_task(bind=True, max_retries=3)
|
|
def generate_photo_thumbnails(self, photo_id, variants=None):
|
|
"""
|
|
Generate thumbnails for a photo on demand.
|
|
|
|
This can be used to regenerate thumbnails if the original
|
|
is updated or if new variants are needed.
|
|
|
|
Args:
|
|
photo_id: ID of the Photo
|
|
variants: List of variant names to generate (None = all)
|
|
|
|
Returns:
|
|
dict: Generated variants and their URLs
|
|
"""
|
|
from apps.media.models import Photo
|
|
from apps.media.services import CloudFlareService
|
|
|
|
try:
|
|
photo = Photo.objects.get(id=photo_id)
|
|
cloudflare = CloudFlareService()
|
|
|
|
if variants is None:
|
|
variants = ['public', 'thumbnail', 'banner']
|
|
|
|
result = {}
|
|
for variant in variants:
|
|
url = cloudflare.get_image_url(photo.cloudflare_image_id, variant)
|
|
result[variant] = url
|
|
|
|
logger.info(f"Generated thumbnails for photo {photo_id}: {variants}")
|
|
return result
|
|
|
|
except Photo.DoesNotExist:
|
|
logger.error(f"Photo {photo_id} not found")
|
|
raise
|
|
except Exception as exc:
|
|
logger.error(f"Error generating thumbnails for photo {photo_id}: {str(exc)}")
|
|
raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries))
|
|
|
|
|
|
@shared_task(bind=True, max_retries=2)
|
|
def cleanup_orphaned_cloudflare_images(self):
|
|
"""
|
|
Clean up CloudFlare images that no longer have database records.
|
|
|
|
This task helps prevent storage bloat by removing images that
|
|
were uploaded but their database records were deleted.
|
|
|
|
Returns:
|
|
dict: Cleanup statistics
|
|
"""
|
|
from apps.media.models import Photo
|
|
from apps.media.services import CloudFlareService
|
|
|
|
try:
|
|
cloudflare = CloudFlareService()
|
|
|
|
# In a real implementation, you would:
|
|
# 1. Get list of all images from CloudFlare API
|
|
# 2. Check which ones don't have Photo records
|
|
# 3. Delete the orphaned images
|
|
|
|
# For now, just log that the task ran
|
|
logger.info("Orphaned image cleanup task completed (not implemented in mock mode)")
|
|
|
|
return {
|
|
'checked': 0,
|
|
'orphaned': 0,
|
|
'deleted': 0
|
|
}
|
|
|
|
except Exception as exc:
|
|
logger.error(f"Error during orphaned image cleanup: {str(exc)}")
|
|
raise self.retry(exc=exc, countdown=300)
|
|
|
|
|
|
@shared_task
|
|
def update_photo_statistics():
|
|
"""
|
|
Update photo-related statistics across the database.
|
|
|
|
This task can update cached counts, generate reports, etc.
|
|
|
|
Returns:
|
|
dict: Updated statistics
|
|
"""
|
|
from apps.media.models import Photo
|
|
from django.db.models import Count
|
|
|
|
try:
|
|
stats = {
|
|
'total_photos': Photo.objects.count(),
|
|
'pending': Photo.objects.filter(moderation_status='pending').count(),
|
|
'approved': Photo.objects.filter(moderation_status='approved').count(),
|
|
'rejected': Photo.objects.filter(moderation_status='rejected').count(),
|
|
'flagged': Photo.objects.filter(moderation_status='flagged').count(),
|
|
'by_type': dict(
|
|
Photo.objects.values('photo_type').annotate(count=Count('id'))
|
|
.values_list('photo_type', 'count')
|
|
)
|
|
}
|
|
|
|
logger.info(f"Photo statistics updated: {stats}")
|
|
return stats
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error updating photo statistics: {str(e)}")
|
|
raise
|