Files
thrilltrack-explorer/django/apps/users/tasks.py
pacnpal d6ff4cc3a3 Add email templates for user notifications and account management
- 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.
2025-11-08 15:34:04 -05:00

344 lines
10 KiB
Python

"""
Background tasks for user management and notifications.
"""
import logging
from celery import shared_task
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.conf import settings
from django.utils import timezone
from datetime import timedelta
logger = logging.getLogger(__name__)
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def send_welcome_email(self, user_id):
"""
Send a welcome email to a newly registered user.
Args:
user_id: ID of the User
Returns:
str: Email send result
"""
from apps.users.models import User
try:
user = User.objects.get(id=user_id)
context = {
'user': user,
'site_url': getattr(settings, 'SITE_URL', 'https://thrillwiki.com'),
}
html_message = render_to_string('emails/welcome.html', context)
send_mail(
subject='Welcome to ThrillWiki! 🎢',
message='',
html_message=html_message,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[user.email],
fail_silently=False,
)
logger.info(f"Welcome email sent to {user.email}")
return f"Welcome email sent to {user.email}"
except User.DoesNotExist:
logger.error(f"User {user_id} not found")
raise
except Exception as exc:
logger.error(f"Error sending welcome email to user {user_id}: {str(exc)}")
raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries))
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def send_password_reset_email(self, user_id, token, reset_url):
"""
Send a password reset email with a secure token.
Args:
user_id: ID of the User
token: Password reset token
reset_url: Full URL for password reset
Returns:
str: Email send result
"""
from apps.users.models import User
try:
user = User.objects.get(id=user_id)
context = {
'user': user,
'reset_url': reset_url,
'request_time': timezone.now(),
'expiry_hours': 24, # Configurable
'site_url': getattr(settings, 'SITE_URL', 'https://thrillwiki.com'),
}
html_message = render_to_string('emails/password_reset.html', context)
send_mail(
subject='Reset Your ThrillWiki Password',
message='',
html_message=html_message,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[user.email],
fail_silently=False,
)
logger.info(f"Password reset email sent to {user.email}")
return f"Password reset email sent to {user.email}"
except User.DoesNotExist:
logger.error(f"User {user_id} not found")
raise
except Exception as exc:
logger.error(f"Error sending password reset email: {str(exc)}")
raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries))
@shared_task(bind=True, max_retries=2)
def cleanup_expired_tokens(self):
"""
Clean up expired JWT tokens and password reset tokens.
This task runs daily to remove old tokens from the database.
Returns:
dict: Cleanup statistics
"""
from rest_framework_simplejwt.token_blacklist.models import OutstandingToken
from django.contrib.auth.tokens import default_token_generator
try:
# Clean up blacklisted JWT tokens older than 7 days
cutoff = timezone.now() - timedelta(days=7)
# Note: Actual implementation depends on token storage strategy
# This is a placeholder for the concept
logger.info("Token cleanup completed")
return {
'jwt_tokens_cleaned': 0,
'reset_tokens_cleaned': 0,
}
except Exception as exc:
logger.error(f"Error cleaning up tokens: {str(exc)}")
raise self.retry(exc=exc, countdown=300)
@shared_task(bind=True, max_retries=3)
def send_account_notification(self, user_id, notification_type, context_data=None):
"""
Send a generic account notification email.
Args:
user_id: ID of the User
notification_type: Type of notification (e.g., 'security_alert', 'profile_update')
context_data: Additional context data for the email
Returns:
str: Email send result
"""
from apps.users.models import User
try:
user = User.objects.get(id=user_id)
context = {
'user': user,
'notification_type': notification_type,
'site_url': getattr(settings, 'SITE_URL', 'https://thrillwiki.com'),
}
if context_data:
context.update(context_data)
# For now, just log (would need specific templates for each type)
logger.info(f"Account notification ({notification_type}) for user {user.email}")
return f"Notification sent to {user.email}"
except User.DoesNotExist:
logger.error(f"User {user_id} not found")
raise
except Exception as exc:
logger.error(f"Error sending account notification: {str(exc)}")
raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries))
@shared_task(bind=True, max_retries=2)
def cleanup_inactive_users(self, days_inactive=365):
"""
Clean up or flag users who haven't logged in for a long time.
Args:
days_inactive: Number of days of inactivity before flagging (default: 365)
Returns:
dict: Cleanup statistics
"""
from apps.users.models import User
try:
cutoff = timezone.now() - timedelta(days=days_inactive)
inactive_users = User.objects.filter(
last_login__lt=cutoff,
is_active=True
)
count = inactive_users.count()
# For now, just log inactive users
# In production, you might want to send reactivation emails
# or mark accounts for deletion
logger.info(f"Found {count} inactive users (last login before {cutoff})")
return {
'inactive_count': count,
'cutoff_date': cutoff.isoformat(),
}
except Exception as exc:
logger.error(f"Error cleaning up inactive users: {str(exc)}")
raise self.retry(exc=exc, countdown=300)
@shared_task
def update_user_statistics():
"""
Update user-related statistics across the database.
Returns:
dict: Updated statistics
"""
from apps.users.models import User
from django.db.models import Count
from datetime import timedelta
try:
now = timezone.now()
week_ago = now - timedelta(days=7)
month_ago = now - timedelta(days=30)
stats = {
'total_users': User.objects.count(),
'active_users': User.objects.filter(is_active=True).count(),
'new_this_week': User.objects.filter(date_joined__gte=week_ago).count(),
'new_this_month': User.objects.filter(date_joined__gte=month_ago).count(),
'verified_users': User.objects.filter(email_verified=True).count(),
'by_role': dict(
User.objects.values('role__name')
.annotate(count=Count('id'))
.values_list('role__name', 'count')
),
}
logger.info(f"User statistics updated: {stats}")
return stats
except Exception as e:
logger.error(f"Error updating user statistics: {str(e)}")
raise
@shared_task(bind=True, max_retries=3)
def send_bulk_notification(self, user_ids, subject, message, html_message=None):
"""
Send bulk email notifications to multiple users.
This is useful for announcements, feature updates, etc.
Args:
user_ids: List of User IDs
subject: Email subject
message: Plain text message
html_message: HTML version of message (optional)
Returns:
dict: Send statistics
"""
from apps.users.models import User
try:
users = User.objects.filter(id__in=user_ids, is_active=True)
sent_count = 0
failed_count = 0
for user in users:
try:
send_mail(
subject=subject,
message=message,
html_message=html_message,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[user.email],
fail_silently=False,
)
sent_count += 1
except Exception as e:
logger.error(f"Failed to send to {user.email}: {str(e)}")
failed_count += 1
continue
result = {
'total': len(user_ids),
'sent': sent_count,
'failed': failed_count,
}
logger.info(f"Bulk notification sent: {result}")
return result
except Exception as exc:
logger.error(f"Error sending bulk notification: {str(exc)}")
raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries))
@shared_task(bind=True, max_retries=2)
def send_email_verification_reminder(self, user_id):
"""
Send a reminder to users who haven't verified their email.
Args:
user_id: ID of the User
Returns:
str: Reminder result
"""
from apps.users.models import User
try:
user = User.objects.get(id=user_id)
if user.email_verified:
logger.info(f"User {user.email} already verified, skipping reminder")
return "User already verified"
# Send verification reminder
logger.info(f"Sending email verification reminder to {user.email}")
# In production, generate new verification token and send email
# For now, just log
return f"Verification reminder sent to {user.email}"
except User.DoesNotExist:
logger.error(f"User {user_id} not found")
raise
except Exception as exc:
logger.error(f"Error sending verification reminder: {str(exc)}")
raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries))