mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:11:13 -05:00
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.
This commit is contained in:
354
django/apps/entities/tasks.py
Normal file
354
django/apps/entities/tasks.py
Normal file
@@ -0,0 +1,354 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user