279 lines
10 KiB
Python
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 apps.events.models import Event, Registration
|
|
from apps.communications.models import Announcement, NewsletterSubscription
|
|
from apps.communications.utils import send_announcement_email, send_event_reminder, get_announcement_recipients
|
|
from apps.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
|