I am trying to implement a way to send messages to users that are user specific. I have seen the docs where they advice to store the channel_name in the database and remove it on disconnect, but I think that will be a burden on the database as channel_name keeps on changing whenever the user connects to the consumer. So I tried the below method during accept, where I tried to assign the user's user_name as a unique channel name
consumer
class ChatGenericAsyncConsumer(AsyncWebsocketConsumer):
"""this is an async consumer"""
async def connect(self):
self.user = self.scope["user"]
if self.user.is_authenticated:
print(
f"authentication successful connection accepted for {self.user.user_name}"
)
self.username = f"{self.user.user_name}"
self.channel_name = f"{self.user.user_name}"
await self.accept()
else:
print("authentication unsuccessful connection closed")
await self.close(code=4123)
async def receive(self, text_data=None, bytes_data=None):
pass
async def disconnect(self, code):
await self.close(code=4123)
# this is the event handler of 'chat.message'
async def chat_message(self, event):
"""
this method handles the sending of message
to the group.
this is same as chat.message
"""
# sending message to the group
print(event["data"])
await self.send(text_data=event["data"])
and then tried to send a message from outside the consumer, where I try to mimic a situation that for some reason the admin wants to send a user specific message or notification to a specific user. So I coded the below api and used channel_layer.send() to send message to that specific channel name
api
@api_view(["POST"])
@permission_classes([AllowAny])
def send_message_from_admin(request, group_name):
try:
message = request.data.get("message")
username = request.data.get("username")
channel_layer = get_channel_layer()
send_data = {"user": "Admin", "message": message}
async_to_sync(channel_layer.send)(
username, {"type": "chat.message", "data": json.dumps(send_data)}
)
return Response(
{"message": "sent message"}, status=status.HTTP_200_OK
)
except Exception as e:
print(f"An exception occurred {e}")
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
here the username is the same as the channel_name set during connect. But somehow this does not work and the event handler is not called to send the message to the specific channel name and the user does not receive the message.
Please suggest me whether there is something wrong with the code or my understanding of sending user specific messages or notifications
Problem:
I don't think you can set the channel_name
yourself; it is automatically set. So self.channel_name = f"{self.user.user_name}"
might set self.channel_name
, but it will not change the actual channel being used.
Should work
What you can try is to use group_send
instead. The idea is to add the user to a unique group (here, you can use the username as the group name). It's counterintuitive, but now you can send to this group, which only has one user. When the user disconnects, you can remove the user from the group.
async def connect(self):
self.user = self.scope["user"]
if self.user.is_authenticated:
print(
f"authentication successful connection accepted for {self.user.user_name}"
)
self.username = f"{self.user.user_name}"
# Remove this since you can get, but not set the channel_name
# self.channel_name = f"{self.user.user_name}"
# ADD THE USER TO HIS OWN UNIQUE "GROUP"
# NOTE: Here I'm using self.channel_name to GET the channel_name of the
# client, NOT setting it to a chosen value
async_to_sync(self.channel_layer.group_add)(self.user.user_name, self.channel_name)
await self.accept()
else:
print("authentication unsuccessful connection closed")
await self.close(code=4123)
async def receive(self, text_data=None, bytes_data=None):
pass
async def disconnect(self, code):
# REMOVE THE USER FROM HIS UNIQUE GROUP
async_to_sync(self.channel_layer.group_discard)(self.user.user_name, self.channel_name)
# NOT SURE YOU NEED THIS ANYMORE?
await self.close(code=4123)
Now to send date:
# Change this:
# async_to_sync(channel_layer.send)(
# username, {"type": "chat.message", "data":
# json.dumps(send_data)}
# )
# to this:
async_to_sync(channel_layer.group_send)(
username, {"type": "chat.message", "data": json.dumps(send_data)}
)