mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 17:31:09 -05:00
Add comment and reply functionality with preview and notification templates
This commit is contained in:
229
history_tracking/notifications.py
Normal file
229
history_tracking/notifications.py
Normal file
@@ -0,0 +1,229 @@
|
||||
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}"
|
||||
Reference in New Issue
Block a user