How Can I Get Django Global Notifications Via Channels To Work?

I have spent the last month or so on and off trying to get the django channels notification to work. I can't get notifications to work but the chat works as expected. Here is my code...

Consumers.py

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.is_group_chat = 'group_name' in self.scope['url_route']['kwargs']

    if self.is_group_chat:
        group_name = self.scope['url_route']['kwargs'].get('group_name')
        self.group_name = group_name.replace('_', ' ')  # Replace underscores with spaces

        try:
            self.group_chat = await database_sync_to_async(GroupChat.objects.get)(name=self.group_name)
        except GroupChat.DoesNotExist:
            await self.close()
            return
        self.chat_group = f'group_{self.group_name.replace(" ", "_")}'
    else:
        self.recipient_username = self.scope['url_route']['kwargs'].get('recipient_username')
        self.sender = self.scope['user']
        self.conversation_group = self.get_conversation_group_name(
            self.sender.username,
            self.recipient_username
        )

        # Convert recipient_username to a User object and assign it to self.recipient
        try:
            self.recipient = await database_sync_to_async(get_user_model().objects.get)(username=self.recipient_username)
        except get_user_model().DoesNotExist:
            await self.close()
            return

    # Join the group
    if self.is_group_chat:
        await self.channel_layer.group_add(
            self.chat_group,
            self.channel_name
        )
    else:
        await self.channel_layer.group_add(
            self.conversation_group,
            self.channel_name
        )

    await self.accept()

def get_conversation_group_name(self, user1, user2):
    return f'chat_{sorted([user1, user2])[0]}_{sorted([user1, user2])[1]}'

async def disconnect(self, close_code):
    if self.is_group_chat:
        await self.channel_layer.group_discard(
            self.chat_group,
            self.channel_name
        )
    else:
        await self.channel_layer.group_discard(
            self.conversation_group,
            self.channel_name
        )

async def receive(self, text_data):
    text_data_json = json.loads(text_data)
    timestamp = timezone.now().isoformat()
    sender = self.scope['user']
    sender_full_name = await self.get_user_full_name(sender)  # Fetch full name
    message = text_data_json.get('message', '')
    attachment = text_data_json.get('attachment', None)
    message_type = text_data_json.get('type')

    # Ensure sender full name is calculated
    sender_full_name = await self.get_user_full_name(sender)

    if message_type == 'delete_message':
        message_id = text_data_json['message_id']
        await self.delete_message(message_id)

    elif self.is_group_chat:
        # Save the group message
        recipients = self.group_chat.members.all()  # Get all members of the group chat

        # Initialize the message object with default values
        chat_message = {
            'sender': sender.username,
            'sender_pk': sender.pk,
            'sender_full_name': sender_full_name,
            'timestamp': timezone.now().isoformat(),  # Adjust timestamp if necessary
            'message': message,  # The message text
            'attachment': None  # Default to None
        }

        # If an attachment is present, process it
        if attachment:
            file_data = attachment.get('file_data')
            file_name = attachment.get('file_name')
            file_type = attachment.get('file_type')

            # Save the file and get its URL
            file_url = await self.save_file(file_data, file_name, is_group_message=True)

            # Update the message with the attachment URL and other details
            chat_message['attachment'] = {
                'file_url': file_url,  # The URL for downloading the file
                'file_name': file_name,
                'file_type': file_type,
            }

            # Save message with file info
            saved_message = await self.save_group_message(sender, "", recipients=recipients, file_url=file_url)
        else:
            # Save message without file
            saved_message = await self.save_group_message(sender, message, recipients=recipients)

        # Send the message to all group members (only once)
        await self.channel_layer.group_send(
            self.chat_group,
            {
                'type': 'chat_message',
                'message': chat_message['message'],
                'sender': chat_message['sender'],
                'sender_pk': chat_message['sender_pk'],
                'sender_full_name': chat_message['sender_full_name'],
                'timestamp': chat_message['timestamp'],
                'message_id': str(saved_message.id),  # Convert UUID to string
                'is_sender': True,  # This indicates it’s from the sender
                'attachment': chat_message['attachment']  # Include attachment if present
            }
        )

        # Create GroupMessageRead entries for each recipient (except the sender)
        for recipient in recipients:
            if recipient != sender:
                await self.create_group_message_read(saved_message, recipient)

    # Check if both message and attachment are present
    elif message_type == 'message' or message_type == 'attachment' or (message and attachment):
        # Prepare the message object
        chat_message = {
            'sender': sender.username,
            'sender_pk': sender.pk,
            'sender_full_name': sender_full_name,
            'timestamp': timezone.now().isoformat(),  # Adjust timestamp if necessary
            'message': message,  # Message text, could be empty if only attachment
            'attachment': None  # Default to None
        }

        # If an attachment is present, process it
        if attachment:
            file_data = attachment.get('file_data')
            file_name = attachment.get('file_name')
            file_type = attachment.get('file_type')

            # Save the file and get its URL
            file_url = await self.save_file(file_data, file_name)

            # Update the message with file URL
            chat_message['attachment'] = {
                'file_url': file_url,  # The URL for downloading the file
                'file_name': file_name,
                'file_type': file_type,
            }

            # Save message with file info
            saved_message = await self.save_direct_message(sender, "", file_url=file_url)
        else:
            # Save message without file
            saved_message = await self.save_direct_message(sender, message)

        # Broadcast the message (with or without attachment)
        await self.channel_layer.group_send(
            self.conversation_group,
            {
                'type': 'chat_message',
                'message': chat_message['message'],
                'sender': chat_message['sender'],
                'sender_pk': chat_message['sender_pk'],
                'timestamp': saved_message.timestamp.isoformat(),
                'message_id': str(saved_message.id),
                'sender_full_name': chat_message['sender_full_name'],
                'attachment': chat_message['attachment']  # Include attachment if present
            }
        )

async def chat_message(self, event):
    # Check if there's an attachment and include it in the event data
    attachment = event.get('attachment', None)

    # Send the message along with attachment data to WebSocket
    await self.send(text_data=json.dumps({
        'message': event['message'],
        'sender': event['sender'],
        'sender_pk': event['sender_pk'],
        'sender_full_name': event['sender_full_name'],
        'timestamp': event['timestamp'],
        'message_id': event['message_id'],
        'is_sender': event.get('is_sender', False),
        'attachment': attachment  # Include the attachment if there is one
    }))

async def chat_attachment(self, event):
    # Send the file attachment details to WebSocket
    await self.send(text_data=json.dumps({
        'attachment': {
            'file_url': event['file_url'],
            'file_name': event['file_name'],
            'file_type': event['file_type'],
        }
    }))

async def save_attachment(self, file_data, file_name, file_type):
    # Save the file (assuming base64 encoding or binary data)
    file_content = ContentFile(file_data)
    file_path = default_storage.save(f'attachments/{file_name}', file_content)

    # Return the file URL
    file_url = default_storage.url(file_path)
    return file_url

async def send_file_attachment(self, file_url, file_name, file_type):
    # Send file attachment information to group
    if self.is_group_chat:
        await self.channel_layer.group_send(
            self.chat_group,
            {
                'type': 'chat_attachment',
                'file_url': file_url,
                'file_name': file_name,
                'file_type': file_type
            }
        )
    else:
        await self.channel_layer.group_send(
            self.conversation_group,
            {
                'type': 'chat_attachment',
                'file_url': file_url,
                'file_name': file_name,
                'file_type': file_type
            }
        )

@database_sync_to_async
def get_recipients(self):
    # Fetch all members of the group chat
    return self.group_chat.members.all()

@database_sync_to_async
def create_group_message_read(self, group_message, recipient):
    GroupMessageRead.objects.get_or_create(
        group_message=group_message,
        recipient=recipient,
        read=False  # Initially, the message is unread for this user
    )

@database_sync_to_async
def save_direct_message(self, sender, message, file_url=None):
    recipient = get_user_model().objects.get(username=self.recipient_username)

    # If there's a file, save it in the `attachment` field
    if file_url:
        return DirectMessage.objects.create(
            sender=sender,
            recipient=recipient,
            message=message,
            attachment=file_url,  # Attach the file URL
            read=False
        )
    else:
        return DirectMessage.objects.create(
            sender=sender,
            recipient=recipient,
            message=message,
            read=False
        )

@database_sync_to_async
def save_group_message(self, sender, message, recipients, file_url=None):
    # Save the group message to the database
    group_message = GroupMessage.objects.create(
        sender=sender,
        group=self.group_chat,
        message=message,
        timestamp=timezone.now().isoformat(),
        attachment=file_url,  # Pass the attachment URL directly
    )
    # Create GroupMessageRead entries for all recipients (except the sender)
    for recipient in recipients:
        if recipient != sender:
            GroupMessageRead.objects.create(
                group_message=group_message,
                recipient=recipient,
                read=False  # Mark as unread initially
            )
    return group_message



  # Save file method updated to handle different directories for group vs. direct messages
    @database_sync_to_async
def save_file(self, file_data, file_name, is_group_message=False):
    # Decode the base64 file data
    file_content = ContentFile(base64.b64decode(file_data))  # Decode to file content

    # Determine the directory based on message type
    if is_group_message:
        file_path = default_storage.save(f'attachments/group_chat/{file_name}', file_content)  # Group messages go to group_chat folder
    else:
        file_path = default_storage.save(f'attachments/chat/{file_name}', file_content)  # Direct messages go to chat folder

    # Get the URL of the uploaded file
    file_url = default_storage.url(file_path)

    # Ensure the URL doesn't have /media/ prefix
    if file_url.startswith('/media/'):
        file_url = file_url[7:]  # Remove "/media" part

    return file_url

@database_sync_to_async
def update_read_status(self, message, recipient):
    # Update the read status to True for the recipient
    try:
        group_message_read = GroupMessageRead.objects.get(group_message=message, recipient=recipient)
        group_message_read.read = True
        group_message_read.save()
    except GroupMessageRead.DoesNotExist:
        pass  # If the record doesn't exist, no action is needed

async def delete_message(self, message_id):
    try:
        # Check if it's a group chat or direct message
        if self.is_group_chat:
            message = await self.get_group_message(message_id)
            if message:
                # Send the deletion notification to all group members
                await self.channel_layer.group_send(
                    self.chat_group,
                    {
                        'type': 'message_deleted',
                        'message_id': message_id,
                    }
                )
            else:
                print(f"Group message with ID {message_id} not found or does not belong to user.")
        else:
            message = await self.get_message(message_id)
            if message:
                # Send the deletion notification to both sender and recipient
                await self.channel_layer.group_send(
                    self.conversation_group,
                    {
                        'type': 'message_deleted',
                        'message_id': message_id,
                    }
                )
            else:
                print(f"Direct message with ID {message_id} not found or does not belong to user.")
    except Exception as e:
        print(f"Error deleting message: {e}")

# The deletion event handler
async def message_deleted(self, event):
    # When a message deletion is broadcast, delete it on the WebSocket side
    await self.send(text_data=json.dumps({
        'type': 'message_deleted',
        'message_id': event['message_id'],  # Pass the message ID to frontend
    }))

@database_sync_to_async
def get_message(self, message_id):
    try:
        # Fetch the direct message and ensure it's the correct user (sender)
        message = DirectMessage.objects.get(id=message_id, sender=self.scope['user'])
        message.delete()  # Perform the delete
        return message
    except DirectMessage.DoesNotExist:
        return None

@database_sync_to_async
def get_group_message(self, message_id):
    try:
        # Fetch the group message and ensure it belongs to the current user (sender)
        message = GroupMessage.objects.get(id=message_id, sender=self.scope['user'])
        message.delete()  # Perform the delete operation
        return message
    except GroupMessage.DoesNotExist:
        print(f"Group message with ID {message_id} not found for user {self.scope['user']}")
        return None

@database_sync_to_async
def get_user_full_name(self, user):
    return f"{user.first_name} {user.last_name}"

# async def receive(self, text_data):
#     pass

async def send_notification(self, event):
    notification = event["notification"]
    print("Fragrances")
    await self.send(text_data=json.dumps({
        "message": notification,
    }))

class GlobalNotificationConsumer(AsyncWebsocketConsumer):

async def connect(self):
    self.user = self.scope['user']
    self.group_name = f"chat_notifications_{self.user.id}"
    await self.channel_layer.group_add(self.group_name, self.channel_name)
    await self.accept()

async def disconnect(self, close_code):
    await self.channel_layer.group_discard(self.group_name, self.channel_name)

async def receive(self, text_data):
    # Handle the incoming data
    text_data_json = json.loads(text_data)
    notification_type = text_data_json.get('type')

    if notification_type == 'global_notification':
        sender = text_data_json.get('sender')
        message = text_data_json.get('message')

        # Send the notification to the intended receiver only
        receiver_username = text_data_json.get('receiver')

        # Sanitize receiver's username as well
        sanitized_receiver = sanitize_group_name(receiver_username)

        # Log the sanitized receiver's group name for debugging
        print(f"Sending notification to group: global_notifications_{sanitized_receiver}")  # Debugging output

        # Send the notification to the receiver's WebSocket group
        await self.channel_layer.group_send(
            f"global_notifications_{sanitized_receiver}",
            {
                'type': 'send_notification',
                'sender': sender,
                'message': message
            }
        )

   # This method handles the 'notification' type message
async def notification(self, event):
    # Extract notification data
    notification = event['chat_message']

    # Send the notification to the WebSocket
    await self.send(text_data=json.dumps({
        'notification': notification
    }))

My base file...

        var wsScheme = window.location.protocol == "https:" ? "wss" : "ws";
        var wsPath = wsScheme + "://" + window.location.host + "/ws/notifications/";

        var socket = new WebSocket(wsPath);

        socket.onopen = function() {
            console.log('WebSocket connection established.');
        };

        socket.onmessage = function(event) {
            console.log('Message received from WebSocket: ', event.data);  // Log the message
            var data = JSON.parse(event.data);

            // If there's a message, display it
            if (data.message && data.sender) {
                alert("New notification from " + data.sender + ": " + data.message); // Display an alert
            } else {
                console.log('No message data in received WebSocket message.');
            }
        };

        socket.onclose = function() {
            console.log('WebSocket connection closed.');
        };

My URLs.py

websocket_urlpatterns = [
    re_path(r'LevelSet/Chat/ws/group/(?P<group_name>[^/]+)/$', consumers.ChatConsumer.as_asgi()),  # Match group names with spaces
    re_path(r'LevelSet/Chat/ws/direct/(?P<recipient_username>[^/]+)/$', consumers.ChatConsumer.as_asgi()),  # Direct message route
    re_path(r'LevelSet/Chat/ws/notifications/$', consumers.GlobalNotificationConsumer.as_asgi()),
]

My ASGI file...

application = ProtocolTypeRouter(
    {
        'http': get_asgi_application(),
        'websocket': AllowedHostsOriginValidator(
            AuthMiddlewareStack(URLRouter(Chat.routing.websocket_urlpatterns))
        )
    }
)

And in my view...as an example...I try to execute this...

def send_test_notification(user):
channel_layer = get_channel_layer()

# Create a test notification
notification = "This is a test message sent to the WebSocket."
print("driver")
# Send message to the user's WebSocket group
async_to_sync(channel_layer.group_send)(
    f"chat_notifications_{user.id}",
    {
        'type': 'chat_message',
        'notification': notification
    }
)

return HttpResponse("Test notification sent to WebSocket.")  I see the driver print in my console....but the chat_message notification is not popping up or showing on my page...Thanks in advance for any thoughts...
Вернуться на верх