Hello there ! I’m a beginner with django and a total newbie to django channel as well, so it might be a simple mistake.
So I am building a chatbot with django using celery and redis. When I connect to my app, there are two websockets connection instead of only one. This duplicate websocket create duplicates in another aspect of the program. For example at the end of the connection I store the discussion with the chatbot in a SQL database. And because of the two websockets, each discussion is stored two times. There are other duplication issues, so I am almost certain it is a problem with the websockets. Another example is the first message of the chatbot that appear 2 two times (but only after sending the first user message).
Do you have any ideas why there are two websocket here ? And how to remove the duplicated one ? Or maybe the two websockets are normal and the duplication issue comes from elsewhere ?
Note: I Already had this issue yesterday but it resolved itself without any change to the code that I can remember. (I tried to solve the issue in the morning but failed. And after coming back from lunch the issue was nowhere to be seen. )
Here is the connection message when I run the server and connect to it:
Here is my consumers.py file:
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
from django.db import connection
import datetime
from .tasks import get_response
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.accept()
self.session_data = {"data" : []}
def receive(self, text_data):
text_data_json = json.loads(text_data)
get_response.delay(self.channel_name, text_data_json)
async_to_sync(self.channel_layer.send)(
self.channel_name,
{
"type": "chat_message",
"text": {"msg": text_data_json["text"], "source": "user"},
},
)
def chat_message(self, event):
text = event["text"]
self.session_data["data"].append(text)
self.send(text_data=json.dumps({"text": text}))
def disconnect(self, _):
self.save_session_data()
def websocket_disconnect(self, message):
self.save_session_data()
super().websocket_disconnect(message)
def save_session_data(self):
if len(self.session_data["data"]) != 0:
cursor = connection.cursor()
time = datetime.datetime.now().time()
date = datetime.date.today()
val = [date,time,json.dumps(self.session_data)]
sql = "INSERT INTO chatbot_discussion (date, time, discussion) VALUES (%s, %s, %s)"
cursor.execute(sql, val)
cursor.fetchall()
cursor.close()
Here is my routing.py file :
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r"ws/chat/$", consumers.ChatConsumer.as_asgi()),
]
Here is my asgi file:
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
from decouple import config
os.environ.setdefault("DJANGO_SETTINGS_MODULE", config("DJANGO_SETTINGS_MODULE"))
# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.
django_asgi_app = get_asgi_application()
import bot.routing
application = ProtocolTypeRouter(
{
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(URLRouter(bot.routing.websocket_urlpatterns))
),
}
)
And here is the CHANNEL_LAYERS from the settings.py file:
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [config("REDIS_BACKEND")],
},
},
}
EDIT:
Note 2 : I am also a beginner at javascript, I took the code here (Build a ChatBot Using Python, Django - DEV Community) and changed it a little bit.
Here you have my chat.html file with the client side javascript :
{% extends 'base.html' %}
{% block body %}
{% load static %}
<div class="p-6 w-full flex flex-col justify-center">
<h1 class="text-3xl tracking-tight font-light" id="chat-header"></h1>
<div
id="chat-log"
class="mt-4 min-w-25 w-1/3 relative p-6 overflow-y-auto h-[30rem] bg-gray-50 border border-gray-200 rounded-md"
></div>
<div class="mt-4">
<input
id="chat-message-input"
class="min-w-15 w-1/4 py-2 outline-none bg-gray-50 border border-gray-300 text-gray-900 text-sm focus:border-blue-500"
type="text"
placeholder="Écrivez votre question..."
/>
<button
id="chat-message-submit"
class="w-32 py-2 px-4 mr-2 border border-transparent text-sm font-medium rounded-md text-white bg-[#ffc107] hover:bg-[#e7ad00]"
type="submit"
>
Envoyer
</button>
</div>
</div>
{% block scripts %}
<script>
var submitBtn = document.getElementById("chat-message-submit");
var wss_protocol = window.location.protocol == "https:" ? "wss://" : "ws://";
var chatSocket = new WebSocket(
wss_protocol + window.location.host + "/ws/chat/"
);
var messages = [];
chatSocket.onopen = function (e) {
document.querySelector("#chat-header").innerHTML =
"Chatbot v0.1";
var first_message = {
source: "bot",
msg: "First message of the bot"
};
messages.push(first_message);
var str = '<ul class="space-y-2">';
str += `<li class="flex justify-start">
${`<img class="w-12 h-12" src="{% static 'images/bot-icon.png' %}">`}
<div class="relative flex max-w-xl px-4 py-2 rounded-lg shadow-md
${"text-gray-700 bg-white border border-gray-200"}">
<span className="block sans-serif">${first_message.msg}</span></div></li>`;
str += "</ul>";
document.querySelector("#chat-log").innerHTML = str;
};
chatSocket.onmessage = function (e) {
var data = JSON.parse(e.data);
var message = data["text"];
if(message.source=="bot"){submitBtn.removeAttribute('disabled');}
messages.push(message);
var str = '<ul class="space-y-2">';
messages.forEach(function (msg) {
str += `<li class="flex ${
msg.source == "bot" ? "justify-start" : "justify-end"
}">
${
msg.source == "bot"
? `<img class="w-12 h-12" src="{% static 'images/bot-icon.png' %}">`
: ""
}
<div class="relative flex max-w-xl px-4 py-2 rounded-lg shadow-md
${
msg.source == "bot"
? "text-gray-700 bg-white border border-gray-200"
: "bg-[#dc3545] text-white"
}">
<span className="block sans-serif">${msg.msg}</span></div></li>`;
});
str += "</ul>";
document.querySelector("#chat-log").innerHTML = str;
};
chatSocket.onclose = function (e) {
alert("Socket closed unexpectedly, please reload the page.");
};
document.querySelector("#chat-message-input").focus();
document.querySelector("#chat-message-input").onkeyup = function (e) {
if (e.keyCode === 13) {
// enter, return
document.querySelector("#chat-message-submit").click();
}
};
document.querySelector("#chat-message-submit").onclick = function (e) {
submitBtn.setAttribute('disabled','');
var messageInputDom = document.querySelector("#chat-message-input");
var message = messageInputDom.value;
chatSocket.send(
JSON.stringify({
text: message,
})
);
messageInputDom.value = "";
};
</script>
{% endblock %}
{% endblock %}
And here the base.html he extand from :
{% comment %} theme/templates/base.html {% endcomment %}
{% load static %}
<!DOCTYPE html>
<html lang="fr">
<head>
<title>{% block title %}Django Chatbot{% endblock %}</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link rel="stylesheet" type="text/css" href="{% static 'css/output.css' %}">
<link rel="shortcut icon" type="image/jpg" href="{% static 'images/bot-icon.png' %}"/>
</head>
<body>
{% block body %} {% endblock %}
</body>
{% block scripts%}{% endblock %}
</html>
EDIT 2 : Duplications in the database were caused by redundant methods websocket_disconnect and disconnect in the consumer.py and had nothing to do with the duplicated websockets. To solve the issue with websocket duplication look at plopidou response bellow.