""" 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)