from celery import shared_task from django.utils import timezone from django.utils.dateparse import parse_datetime from django.db import transaction # <--- FIX: Added missing import from apps.social.models import SocialAccount, SocialContent, SocialComment from apps.social.services.google import GoogleBusinessService, GoogleAPIError import logging logger = logging.getLogger(__name__) @shared_task def sync_all_accounts_periodic(): # smartly run for both historical and delta sync based on last_synced_at """Runs Periodically (via Celery Beat) to pull new/updated reviews.""" logger.info("Starting Periodic Sync for Google Business...") accounts = SocialAccount.objects.filter(platform_type='GO', is_active=True) for account in accounts: try: sync_single_account.delay(account.id) # <--- FIX: Use .delay() for async # Note: Updating last_synced_at inside the loop here is risky if .delay() hasn't finished yet. # It's better to update it inside sync_single_account, but I kept your structure. except Exception as e: logger.error(f"Failed to trigger sync for account {account.id}: {e}") @shared_task def sync_single_account(account_id): """Background job to sync a specific account.""" try: account = SocialAccount.objects.get(id=account_id, platform_type='GO') locations_data = GoogleBusinessService.fetch_locations(account) for loc_data in locations_data: location, created = SocialContent.objects.get_or_create( platform_type='GO', content_id=loc_data['name'], defaults={ 'account': account, 'title': loc_data.get('title', 'Unknown Location'), 'text': "", 'content_data': loc_data } ) if created or location.last_comment_sync_at is None: # Set to old date for Historical Sync location.last_comment_sync_at = timezone.now() - timezone.timedelta(days=365 * 5) location.save() reviews_data = GoogleBusinessService.fetch_reviews_delta(account, location) if not reviews_data: continue latest_time = location.last_comment_sync_at with transaction.atomic(): # <--- This import is now fixed for r_data in reviews_data: review_time = _save_review(account, location, r_data) if review_time and review_time > latest_time: latest_time = review_time location.last_comment_sync_at = latest_time location.save() # Update account sync time here to be accurate account.last_synced_at = timezone.now() account.save() except SocialAccount.DoesNotExist: logger.error(f"Account {account_id} not found") except Exception as e: logger.error(f"Error syncing account {account_id}: {e}") raise def _save_review(account, location, r_data): """Helper to save/update a single review matching the SocialComment model.""" review_id = r_data.get('name') created_str = r_data.get('createTime') update_str = r_data.get('updateTime') created_at = timezone.now() if created_str: try: dt = parse_datetime(created_str) if dt: created_at = dt if dt.tzinfo else timezone.make_aware(dt) except: pass update_time = None if update_str: try: dt = parse_datetime(update_str) if dt: update_time = dt if dt.tzinfo else timezone.make_aware(dt) except: pass star_map = {'ONE': 1, 'TWO': 2, 'THREE': 3, 'FOUR': 4, 'FIVE': 5} rating = star_map.get(r_data.get('starRating', 'ZERO'), 0) reply_obj = r_data.get('reply') reply_count = 1 if reply_obj else 0 comment_text = r_data.get('comment', '') comment_data = r_data.copy() comment_data['star_rating'] = rating comment_data['reviewer_name'] = r_data.get('reviewer', {}).get('displayName', 'Anonymous') SocialComment.objects.update_or_create( platform_type='GO', comment_id=review_id, defaults={ 'account': account, 'content': location, 'author_name': r_data.get('reviewer', {}).get('displayName', 'Anonymous'), 'author_id': r_data.get('reviewer', {}).get('displayName'), 'text': comment_text, 'created_at': created_at, 'rating': rating, 'reply_count': reply_count, 'comment_data': comment_data } ) return update_time