Introduce notifications app and integrate emails
Add a new notifications app (models, serializers, views, admin, tasks, consumers, routing, urls, tests) with Notification.notify for in-app websocket pushes and optional email delivery. Centralize email sending in notifications.tasks.send_email_with_context and re-export it from account.tasks for compatibility. Update account and commerce and advertisement tasks to use Notification.notify/_notify_order and the new email helper; adjust order/notification tasks to consolidate logic. Wire notifications into ASGI routing, settings and URL conf. Misc: handle OSError when importing weasyprint, add tmp/ to .gitignore, add local .claude PowerShell checks, add social.blog skeleton, and remove legacy ews-component test files.
This commit is contained in:
93
backend/notifications/consumers.py
Normal file
93
backend/notifications/consumers.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import json
|
||||
from channels.db import database_sync_to_async
|
||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
@database_sync_to_async
|
||||
def _mark_notification_read(notification_id, user):
|
||||
from .models import Notification
|
||||
try:
|
||||
n = Notification.objects.get(pk=notification_id, user=user)
|
||||
if not n.is_read:
|
||||
n.is_read = True
|
||||
n.read_at = timezone.now()
|
||||
n.save(update_fields=["is_read", "read_at"])
|
||||
return n.read_at.isoformat()
|
||||
except Notification.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
@database_sync_to_async
|
||||
def _mark_all_notifications_read(user):
|
||||
from .models import Notification
|
||||
now = timezone.now()
|
||||
return Notification.objects.filter(user=user, is_read=False).update(is_read=True, read_at=now)
|
||||
|
||||
|
||||
@database_sync_to_async
|
||||
def _delete_notification(notification_id, user):
|
||||
from .models import Notification
|
||||
try:
|
||||
n = Notification.objects.get(pk=notification_id, user=user)
|
||||
n.delete()
|
||||
return True
|
||||
except Notification.DoesNotExist:
|
||||
return False
|
||||
|
||||
|
||||
class NotificationConsumer(AsyncWebsocketConsumer):
|
||||
|
||||
async def connect(self):
|
||||
user = self.scope["user"]
|
||||
if not user.is_authenticated:
|
||||
await self.close(code=4401)
|
||||
return
|
||||
self.group_name = f"notifications_{user.pk}"
|
||||
await self.channel_layer.group_add(self.group_name, self.channel_name)
|
||||
await self.accept()
|
||||
|
||||
async def disconnect(self, close_code):
|
||||
if hasattr(self, "group_name"):
|
||||
await self.channel_layer.group_discard(self.group_name, self.channel_name)
|
||||
|
||||
async def receive(self, text_data):
|
||||
data = json.loads(text_data)
|
||||
msg_type = data.get("type")
|
||||
|
||||
if msg_type == "mark_read":
|
||||
notification_id = data.get("id")
|
||||
read_at = await _mark_notification_read(notification_id, self.scope["user"])
|
||||
if read_at:
|
||||
await self.send(text_data=json.dumps({
|
||||
"type": "notification.read",
|
||||
"id": notification_id,
|
||||
"read_at": read_at,
|
||||
}))
|
||||
|
||||
elif msg_type == "mark_all_read":
|
||||
count = await _mark_all_notifications_read(self.scope["user"])
|
||||
await self.send(text_data=json.dumps({
|
||||
"type": "notification.read_all",
|
||||
"marked": count,
|
||||
}))
|
||||
|
||||
elif msg_type == "delete":
|
||||
notification_id = data.get("id")
|
||||
deleted = await _delete_notification(notification_id, self.scope["user"])
|
||||
if deleted:
|
||||
await self.send(text_data=json.dumps({
|
||||
"type": "notification.deleted",
|
||||
"id": notification_id,
|
||||
}))
|
||||
|
||||
async def notification_new(self, event):
|
||||
await self.send(text_data=json.dumps({
|
||||
"type": "notification.new",
|
||||
"id": event["id"],
|
||||
"title": event["title"],
|
||||
"text": event["text"],
|
||||
"notification_type": event["notification_type"],
|
||||
"action_url": event.get("action_url"),
|
||||
"created_at": event["created_at"],
|
||||
}))
|
||||
Reference in New Issue
Block a user