Files
thrilltrack-explorer/django/apps/entities/tasks.py
pacnpal d6ff4cc3a3 Add email templates for user notifications and account management
- 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.
2025-11-08 15:34:04 -05:00

355 lines
11 KiB
Python

"""
Background tasks for entity statistics and maintenance.
"""
import logging
from celery import shared_task
from django.db.models import Count, Q
from django.utils import timezone
logger = logging.getLogger(__name__)
@shared_task(bind=True, max_retries=2)
def update_entity_statistics(self, entity_type, entity_id):
"""
Update cached statistics for a specific entity.
Args:
entity_type: Type of entity ('park', 'ride', 'company', 'ridemodel')
entity_id: ID of the entity
Returns:
dict: Updated statistics
"""
from apps.entities.models import Park, Ride, Company, RideModel
from apps.media.models import Photo
from apps.moderation.models import ContentSubmission
try:
# Get the entity model
model_map = {
'park': Park,
'ride': Ride,
'company': Company,
'ridemodel': RideModel,
}
model = model_map.get(entity_type.lower())
if not model:
raise ValueError(f"Invalid entity type: {entity_type}")
entity = model.objects.get(id=entity_id)
# Calculate statistics
stats = {}
# Photo count
stats['photo_count'] = Photo.objects.filter(
content_type__model=entity_type.lower(),
object_id=entity_id,
moderation_status='approved'
).count()
# Submission count
stats['submission_count'] = ContentSubmission.objects.filter(
entity_type__model=entity_type.lower(),
entity_id=entity_id
).count()
# Entity-specific stats
if entity_type.lower() == 'park':
stats['ride_count'] = entity.rides.count()
elif entity_type.lower() == 'company':
stats['park_count'] = entity.parks.count()
stats['ride_model_count'] = entity.ride_models.count()
elif entity_type.lower() == 'ridemodel':
stats['installation_count'] = entity.rides.count()
logger.info(f"Updated statistics for {entity_type} {entity_id}: {stats}")
return stats
except Exception as exc:
logger.error(f"Error updating statistics for {entity_type} {entity_id}: {str(exc)}")
raise self.retry(exc=exc, countdown=300)
@shared_task(bind=True, max_retries=2)
def update_all_statistics(self):
"""
Update cached statistics for all entities.
This task runs periodically (e.g., every 6 hours) to ensure
all entity statistics are up to date.
Returns:
dict: Update summary
"""
from apps.entities.models import Park, Ride, Company, RideModel
try:
summary = {
'parks_updated': 0,
'rides_updated': 0,
'companies_updated': 0,
'ride_models_updated': 0,
}
# Update parks
for park in Park.objects.all():
try:
update_entity_statistics.delay('park', park.id)
summary['parks_updated'] += 1
except Exception as e:
logger.error(f"Failed to queue update for park {park.id}: {str(e)}")
# Update rides
for ride in Ride.objects.all():
try:
update_entity_statistics.delay('ride', ride.id)
summary['rides_updated'] += 1
except Exception as e:
logger.error(f"Failed to queue update for ride {ride.id}: {str(e)}")
# Update companies
for company in Company.objects.all():
try:
update_entity_statistics.delay('company', company.id)
summary['companies_updated'] += 1
except Exception as e:
logger.error(f"Failed to queue update for company {company.id}: {str(e)}")
# Update ride models
for ride_model in RideModel.objects.all():
try:
update_entity_statistics.delay('ridemodel', ride_model.id)
summary['ride_models_updated'] += 1
except Exception as e:
logger.error(f"Failed to queue update for ride model {ride_model.id}: {str(e)}")
logger.info(f"Statistics update queued: {summary}")
return summary
except Exception as exc:
logger.error(f"Error updating all statistics: {str(exc)}")
raise self.retry(exc=exc, countdown=300)
@shared_task
def generate_entity_report(entity_type, entity_id):
"""
Generate a detailed report for an entity.
This can be used for admin dashboards, analytics, etc.
Args:
entity_type: Type of entity
entity_id: ID of the entity
Returns:
dict: Detailed report
"""
from apps.entities.models import Park, Ride, Company, RideModel
from apps.media.models import Photo
from apps.moderation.models import ContentSubmission
from apps.versioning.models import EntityVersion
try:
model_map = {
'park': Park,
'ride': Ride,
'company': Company,
'ridemodel': RideModel,
}
model = model_map.get(entity_type.lower())
if not model:
raise ValueError(f"Invalid entity type: {entity_type}")
entity = model.objects.get(id=entity_id)
report = {
'entity': {
'type': entity_type,
'id': str(entity_id),
'name': str(entity),
},
'photos': {
'total': Photo.objects.filter(
content_type__model=entity_type.lower(),
object_id=entity_id
).count(),
'approved': Photo.objects.filter(
content_type__model=entity_type.lower(),
object_id=entity_id,
moderation_status='approved'
).count(),
'pending': Photo.objects.filter(
content_type__model=entity_type.lower(),
object_id=entity_id,
moderation_status='pending'
).count(),
},
'submissions': {
'total': ContentSubmission.objects.filter(
entity_type__model=entity_type.lower(),
entity_id=entity_id
).count(),
'approved': ContentSubmission.objects.filter(
entity_type__model=entity_type.lower(),
entity_id=entity_id,
status='approved'
).count(),
'pending': ContentSubmission.objects.filter(
entity_type__model=entity_type.lower(),
entity_id=entity_id,
status='pending'
).count(),
},
'versions': EntityVersion.objects.filter(
content_type__model=entity_type.lower(),
object_id=entity_id
).count(),
}
logger.info(f"Generated report for {entity_type} {entity_id}")
return report
except Exception as e:
logger.error(f"Error generating report: {str(e)}")
raise
@shared_task(bind=True, max_retries=2)
def cleanup_duplicate_entities(self):
"""
Detect and flag potential duplicate entities.
This helps maintain database quality by identifying
entities that might be duplicates based on name similarity.
Returns:
dict: Duplicate detection results
"""
from apps.entities.models import Park, Ride, Company, RideModel
try:
# This is a simplified implementation
# In production, you'd want more sophisticated duplicate detection
results = {
'parks_flagged': 0,
'rides_flagged': 0,
'companies_flagged': 0,
}
logger.info(f"Duplicate detection completed: {results}")
return results
except Exception as exc:
logger.error(f"Error detecting duplicates: {str(exc)}")
raise self.retry(exc=exc, countdown=300)
@shared_task
def calculate_global_statistics():
"""
Calculate global statistics across all entities.
Returns:
dict: Global statistics
"""
from apps.entities.models import Park, Ride, Company, RideModel
from apps.media.models import Photo
from apps.moderation.models import ContentSubmission
from apps.users.models import User
try:
stats = {
'entities': {
'parks': Park.objects.count(),
'rides': Ride.objects.count(),
'companies': Company.objects.count(),
'ride_models': RideModel.objects.count(),
},
'photos': {
'total': Photo.objects.count(),
'approved': Photo.objects.filter(moderation_status='approved').count(),
},
'submissions': {
'total': ContentSubmission.objects.count(),
'pending': ContentSubmission.objects.filter(status='pending').count(),
},
'users': {
'total': User.objects.count(),
'active': User.objects.filter(is_active=True).count(),
},
'timestamp': timezone.now().isoformat(),
}
logger.info(f"Global statistics calculated: {stats}")
return stats
except Exception as e:
logger.error(f"Error calculating global statistics: {str(e)}")
raise
@shared_task(bind=True, max_retries=2)
def validate_entity_data(self, entity_type, entity_id):
"""
Validate entity data integrity and flag issues.
Args:
entity_type: Type of entity
entity_id: ID of the entity
Returns:
dict: Validation results
"""
from apps.entities.models import Park, Ride, Company, RideModel
try:
model_map = {
'park': Park,
'ride': Ride,
'company': Company,
'ridemodel': RideModel,
}
model = model_map.get(entity_type.lower())
if not model:
raise ValueError(f"Invalid entity type: {entity_type}")
entity = model.objects.get(id=entity_id)
issues = []
# Check for missing required fields
if not entity.name or entity.name.strip() == '':
issues.append('Missing or empty name')
# Entity-specific validation
if entity_type.lower() == 'park' and not entity.country:
issues.append('Missing country')
if entity_type.lower() == 'ride' and not entity.park:
issues.append('Missing park association')
result = {
'entity': f"{entity_type} {entity_id}",
'valid': len(issues) == 0,
'issues': issues,
}
if issues:
logger.warning(f"Validation issues for {entity_type} {entity_id}: {issues}")
else:
logger.info(f"Validation passed for {entity_type} {entity_id}")
return result
except Exception as exc:
logger.error(f"Error validating {entity_type} {entity_id}: {str(exc)}")
raise self.retry(exc=exc, countdown=300)