Files
CE-Association-Website/backend/communications/tasks.py
Amirhossein Khalili 7a8ddeabed
Some checks failed
CI/CD / Backend & Frontend Checks (push) Has been cancelled
CI/CD / Deploy to Production (push) Has been cancelled
init
2026-05-18 11:34:07 +03:30

279 lines
10 KiB
Python

from django.utils import timezone
from django.contrib.auth import get_user_model
import logging
from celery import shared_task
from datetime import timedelta
from events.models import Event, Registration
from communications.models import Announcement, NewsletterSubscription
from communications.utils import send_announcement_email, send_event_reminder, get_announcement_recipients
from communications.push_notifications import push_service
User = get_user_model()
logger = logging.getLogger(__name__)
SYSTEM_USER_ID = 1
@shared_task(bind=True, max_retries=3)
def send_announcement_notifications(self, announcement_id):
"""Send email and push notifications for an announcement"""
try:
announcement = Announcement.objects.get(id=announcement_id)
# Send email notifications
if announcement.send_email and not announcement.email_sent:
recipients = get_announcement_recipients(announcement)
if recipients:
success = send_announcement_email(announcement, recipients)
if success:
announcement.email_sent = True
announcement.save()
logger.info(f"Email notifications sent for announcement {announcement.id}")
# Send push notifications
if announcement.send_push and not announcement.push_sent:
sent_count = push_service.send_announcement_notification(announcement)
if sent_count > 0:
announcement.push_sent = True
announcement.save()
logger.info(f"Push notifications sent to {sent_count} devices for announcement {announcement.id}")
return f"Notifications sent for announcement: {announcement.title}"
except Announcement.DoesNotExist:
logger.error(f"Announcement {announcement_id} not found")
return f"Announcement {announcement_id} not found"
except Exception as exc:
logger.error(f"Failed to send announcement notifications: {exc}")
raise self.retry(exc=exc, countdown=60)
@shared_task(bind=True, max_retries=3)
def send_newsletter_confirmation_task(self, subscription_id):
"""Send newsletter confirmation email"""
try:
from .utils import send_newsletter_confirmation
subscription = NewsletterSubscription.objects.get(id=subscription_id)
success = send_newsletter_confirmation(subscription)
if success:
logger.info(f"Newsletter confirmation sent to {subscription.email}")
return f"Newsletter confirmation sent to {subscription.email}"
else:
raise Exception("Failed to send newsletter confirmation")
except NewsletterSubscription.DoesNotExist:
logger.error(f"Newsletter subscription {subscription_id} not found")
return f"Newsletter subscription {subscription_id} not found"
except Exception as exc:
logger.error(f"Failed to send newsletter confirmation: {exc}")
raise self.retry(exc=exc, countdown=60)
@shared_task
def send_event_reminders():
"""Send reminders for events starting about 24 hours from now within a 30-minute window."""
try:
reminder_target = timezone.now() + timedelta(hours=24)
window = timedelta(minutes=30)
start_range = reminder_target - window
end_range = reminder_target + window
events = Event.objects.filter(
start_time__range=(start_range, end_range),
status='published',
is_deleted=False
)
total_sent = 0
for event in events:
# Get confirmed registrations
registrations = Registration.objects.filter(
event=event,
status='confirmed',
is_deleted=False
).select_related('user')
for registration in registrations:
try:
# Send email reminder
send_event_reminder(event, registration.user)
# Send push notification reminder
push_service.send_event_reminder_notification(event, registration.user)
total_sent += 1
except Exception as e:
logger.error(f"Failed to send reminder to {registration.user.email}: {str(e)}")
logger.info(f"Event reminders sent to {total_sent} users")
return f"Event reminders sent to {total_sent} users"
except Exception as exc:
logger.error(f"Failed to send event reminders: {exc}")
raise exc
@shared_task
def send_weekly_newsletter():
"""Send the weekly newsletter as the system user with recent announcements and upcoming events."""
try:
# Get active newsletter subscribers
subscribers = NewsletterSubscription.objects.filter(
is_active=True,
confirmed_at__isnull=False,
is_deleted=False
)
if not subscribers.exists():
logger.info("No active newsletter subscribers found")
return "No active newsletter subscribers found"
# Get recent announcements (last 7 days)
week_ago = timezone.now() - timedelta(days=7)
recent_announcements = Announcement.objects.filter(
is_published=True,
publish_date__gte=week_ago,
announcement_type__in=['general', 'academic', 'newsletter'],
is_deleted=False
).order_by('-publish_date')[:5]
# Get upcoming events (next 14 days)
two_weeks_ahead = timezone.now() + timedelta(days=14)
upcoming_events = Event.objects.filter(
start_time__range=(timezone.now(), two_weeks_ahead),
status='published',
is_deleted=False
).order_by('start_time')[:5]
newsletter_content = f"""
# Weekly Newsletter - {timezone.now().strftime('%B %d, %Y')}
## Recent Announcements
"""
for announcement in recent_announcements:
newsletter_content += f"- **{announcement.title}** ({announcement.publish_date.strftime('%B %d')})\n"
newsletter_content += "\n## Upcoming Events\n"
for event in upcoming_events:
newsletter_content += f"- **{event.title}** - {event.start_time.strftime('%B %d, %Y at %I:%M %p')}\n"
if not recent_announcements.exists() and not upcoming_events.exists():
newsletter_content += "\nNo recent announcements or upcoming events this week."
newsletter = Announcement.objects.create(
title=f"Weekly Newsletter - {timezone.now().strftime('%B %d, %Y')}",
content=newsletter_content,
announcement_type='newsletter',
priority='normal',
author_id=SYSTEM_USER_ID,
is_published=True,
publish_date=timezone.now(),
send_email=True,
target_audience='subscribers'
)
# Send to subscribers
subscriber_emails = list(subscribers.values_list('email', flat=True))
success = send_announcement_email(newsletter, subscriber_emails)
if success:
newsletter.email_sent = True
newsletter.save()
logger.info(f"Weekly newsletter sent to {len(subscriber_emails)} subscribers")
return f"Weekly newsletter sent to {len(subscriber_emails)} subscribers"
else:
raise Exception("Failed to send weekly newsletter")
except Exception as exc:
logger.error(f"Failed to send weekly newsletter: {exc}")
raise exc
@shared_task
def cleanup_expired_tokens():
"""Clean up expired newsletter confirmation tokens"""
try:
# Remove unconfirmed subscriptions older than 7 days
week_ago = timezone.now() - timedelta(days=7)
expired_subscriptions = NewsletterSubscription.objects.filter(
confirmed_at__isnull=True,
created_at__lt=week_ago
)
count = expired_subscriptions.count()
expired_subscriptions.delete()
logger.info(f"Cleaned up {count} expired newsletter subscriptions")
return f"Cleaned up {count} expired newsletter subscriptions"
except Exception as exc:
logger.error(f"Failed to cleanup expired tokens: {exc}")
raise exc
@shared_task
def send_bulk_announcement(announcement_id, recipient_emails):
"""Send announcement to a specific list of recipients"""
try:
announcement = Announcement.objects.get(id=announcement_id)
# Split recipients into batches to avoid overwhelming the email server
batch_size = 50
total_sent = 0
for i in range(0, len(recipient_emails), batch_size):
batch = recipient_emails[i:i + batch_size]
success = send_announcement_email(announcement, batch)
if success:
total_sent += len(batch)
logger.info(f"Sent announcement to batch of {len(batch)} recipients")
# Small delay between batches
import time
time.sleep(1)
logger.info(f"Bulk announcement sent to {total_sent} recipients")
return f"Bulk announcement sent to {total_sent} recipients"
except Exception as exc:
logger.error(f"Failed to send bulk announcement: {exc}")
raise exc
@shared_task
def process_scheduled_announcements():
"""Process announcements scheduled for publication"""
try:
now = timezone.now()
# Get announcements scheduled for publication
scheduled_announcements = Announcement.objects.filter(
is_published=True,
publish_date__lte=now,
email_sent=False,
send_email=True,
is_deleted=False
)
processed_count = 0
for announcement in scheduled_announcements:
# Send notifications
send_announcement_notifications.delay(announcement.id)
processed_count += 1
logger.info(f"Processed {processed_count} scheduled announcements")
return f"Processed {processed_count} scheduled announcements"
except Exception as exc:
logger.error(f"Failed to process scheduled announcements: {exc}")
raise exc