mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:11: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.
355 lines
11 KiB
Python
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)
|