Files
thrillwiki_django_no_react/history_tracking/notifications.py

229 lines
7.7 KiB
Python

from django.core.mail import send_mail
from django.conf import settings
from django.template.loader import render_to_string
from django.utils import timezone
from django.contrib.auth import get_user_model
import requests
import json
from datetime import timedelta
from celery import shared_task
User = get_user_model()
class NotificationDispatcher:
"""Handles comment notifications and escalations"""
def __init__(self):
self.email_enabled = hasattr(settings, 'EMAIL_HOST')
self.slack_enabled = hasattr(settings, 'SLACK_WEBHOOK_URL')
self.sms_enabled = hasattr(settings, 'SMS_API_KEY')
def notify_new_comment(self, comment, thread):
"""Handle notification for a new comment"""
# Queue immediate notifications
self.send_in_app_notification.delay(
user_ids=self._get_thread_participants(thread),
title="New Comment",
message=f"New comment on {thread.content_object}",
link=self._get_thread_url(thread)
)
# Queue email notifications
self.send_email_notification.delay(
user_ids=self._get_thread_participants(thread),
subject=f"New comment on {thread.content_object}",
template="notifications/new_comment.html",
context={
'comment': comment,
'thread': thread,
'url': self._get_thread_url(thread)
}
)
# Schedule Slack escalation if needed
if self.slack_enabled:
self.schedule_slack_escalation.apply_async(
args=[comment.id],
countdown=24 * 3600 # 24 hours
)
def notify_mention(self, comment, mentioned_users):
"""Handle notification for @mentions"""
user_ids = [user.id for user in mentioned_users]
# Queue immediate notifications
self.send_in_app_notification.delay(
user_ids=user_ids,
title="Mentioned in Comment",
message=f"{comment.author} mentioned you in a comment",
link=self._get_comment_url(comment)
)
# Queue email notifications
self.send_email_notification.delay(
user_ids=user_ids,
subject="You were mentioned in a comment",
template="notifications/mention.html",
context={
'comment': comment,
'url': self._get_comment_url(comment)
}
)
# Queue mobile push notifications
self.send_push_notification.delay(
user_ids=user_ids,
title="New Mention",
message=f"{comment.author} mentioned you: {comment.content[:100]}..."
)
# Schedule SMS escalation if needed
if self.sms_enabled:
self.schedule_sms_escalation.apply_async(
args=[comment.id, user_ids],
countdown=12 * 3600 # 12 hours
)
def notify_resolution(self, thread, resolver):
"""Handle notification for thread resolution"""
self.send_in_app_notification.delay(
user_ids=self._get_thread_participants(thread),
title="Thread Resolved",
message=f"Thread resolved by {resolver}",
link=self._get_thread_url(thread)
)
@shared_task
def send_in_app_notification(user_ids, title, message, link):
"""Send in-app notification to users"""
from .models import InAppNotification
for user_id in user_ids:
InAppNotification.objects.create(
user_id=user_id,
title=title,
message=message,
link=link
)
@shared_task
def send_email_notification(user_ids, subject, template, context):
"""Send email notification to users"""
if not settings.EMAIL_HOST:
return
users = User.objects.filter(id__in=user_ids)
for user in users:
if not user.email:
continue
html_content = render_to_string(template, {
'user': user,
**context
})
send_mail(
subject=subject,
message='',
html_message=html_content,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[user.email]
)
@shared_task
def send_push_notification(user_ids, title, message):
"""Send mobile push notification"""
from .models import PushToken
tokens = PushToken.objects.filter(
user_id__in=user_ids,
active=True
)
if not tokens:
return
# Implementation depends on push notification service
# Example using Firebase:
try:
requests.post(
settings.FIREBASE_FCM_URL,
headers={
'Authorization': f'key={settings.FIREBASE_SERVER_KEY}',
'Content-Type': 'application/json'
},
json={
'registration_ids': [t.token for t in tokens],
'notification': {
'title': title,
'body': message
}
}
)
except Exception as e:
print(f"Push notification failed: {e}")
@shared_task
def schedule_slack_escalation(comment_id):
"""Send Slack DM escalation for unread comments"""
from .models import Comment
try:
comment = Comment.objects.get(id=comment_id)
if not comment.read_by.exists():
# Send Slack message
requests.post(
settings.SLACK_WEBHOOK_URL,
json={
'text': (
f"Unread comment needs attention:\n"
f"{comment.content}\n"
f"View: {self._get_comment_url(comment)}"
)
}
)
except Exception as e:
print(f"Slack escalation failed: {e}")
@shared_task
def schedule_sms_escalation(comment_id, user_ids):
"""Send SMS escalation for unread mentions"""
from .models import Comment
try:
comment = Comment.objects.get(id=comment_id)
users = User.objects.filter(id__in=user_ids)
for user in users:
if not user.phone_number:
continue
if not comment.read_by.filter(id=user.id).exists():
# Send SMS using Twilio or similar service
requests.post(
settings.SMS_API_URL,
headers={'Authorization': f'Bearer {settings.SMS_API_KEY}'},
json={
'to': user.phone_number,
'message': (
f"You were mentioned in a comment that needs attention. "
f"View: {self._get_comment_url(comment)}"
)
}
)
except Exception as e:
print(f"SMS escalation failed: {e}")
def _get_thread_participants(self, thread):
"""Get IDs of all participants in a thread"""
return list(set(
[thread.created_by_id] +
list(thread.comments.values_list('author_id', flat=True))
))
def _get_thread_url(self, thread):
"""Generate URL for thread"""
return f"/version-control/comments/thread/{thread.id}/"
def _get_comment_url(self, comment):
"""Generate URL for specific comment"""
return f"{self._get_thread_url(comment.thread)}#comment-{comment.id}"