Unable to create POST request via HTMX for WebSocket
When trying to send a form in real-time chat, a GET request (HTTP GET /?csrfmiddlewaretoken=some_csrf) appears in the console instead of POST, and the program does not reach the consumer. Django-v5.2.4, daphne-v4.2.1
chat.html
{% extends 'layouts/blank.html' %}
{% block content %}
<wrapper class="block max-w-2xl mx-auto my-10 px-6">
{% if chat_group.groupchat_name %}
<div class="flex justify-between">
<h2>{{ chat_group.groupchat_name }}</h2>
{% if user == chat_group.admin %}
<a href="{% url 'edit-chatroom' chat_group.group_name %}">
<div class="p-2 bg-gray-200 hover:bg-blue-600 rounded-lg group">
<svg class="fill-gray-500 group-hover:fill-white" width="16" height="16"
<path d="M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm.176 4.823L9.75 4.81l-6.286 6.287a.253.253 0 0 0-.064.108l-.558 1.953 1.953-.558a.253.253 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z"></path>
</svg>
</div>
</a>
{% endif %}
</div>
{% endif %}
<div id="chat_window" class="h-[45rem] flex flex-col bg-gray-800 rounded-2xl shadow-2xl relative p-1">
<div class="flex justify-center text-emerald-400 bg-gray-800 p-2 sticky top-0 z-10">
{% if other_user %}
<div id="online-icon" class="gray-dot absolute top-2 left-2"></div>
<a href="{% url 'profile' other_user.username %}">
<div class="flex items-center gap-2 p-4 sticky top-0 z-10">
<img class="w-10 h-10 rounded-full object-cover" src="{{ other_user.profile.avatar }}" />
<div>
<span class="font-bold text-white">{{ other_user.profile.name }}</span>
<span class="text-sm font-light text-gray-400">@{{ other_user.username }}</span>
</div>
</div>
</a>
{% elif chat_group.groupchat_name %}
<ul id="groupchat-members" class="flex gap-4">
{% for member in chat_group.members.all %}
<li>
<a href="{% url 'profile' member.username %}" class="flex flex-col text-gray-400 items-center justify-center w-20 gap-2">
<img src="{{ member.profile.avatar }}" class="w-14 h-14 rounded-full object-cover" />
{{ member.profile.name|slice:":10" }}
</a>
</li>
{% endfor %}
</ul>
{% else %}
<div id="online-icon"></div>
<span id="online-count" class="pr-1"></span>online
{% endif %}
</div>
<div id='chat_container' class="overflow-y-auto grow">
<ul id='chat_messages' class="flex flex-col justify-end gap-2 p-4">
{% for message in chat_messages reversed %}
{% include 'a_rtchat/chat_message.html' %}
{% endfor %}
</ul>
</div>
<div class="sticky bottom-0 z-10 p-2 bg-gray-800">
<div class="flex flex-col gap-4 items-center rounded-xl px-2 py-2">
<form id="chat_message_form" class="w-full"
hx-ext="ws"
ws-connect="/ws/chatroom/public-chat"
ws-send
_="on htmx:wsAfterSend reset() me">
{% csrf_token %}
{{ form }}
</form>
</div>
</div>
</div>
</wrapper>
{% endblock %}
{% block javascript %}
<script>
function scrollToBottom() {
const container = document.getElementById('chat_container');
container.scrollTop = container.scrollHeight;
}
scrollToBottom()
</script>
{% endblock javascript %}
routing.py
from django.urls import path
from .consumers import *
websocket_urlpatterns = [
path("ws/chatroom/<chatroom_name>", ChatroomConsumer.as_asgi()),
]
consumers.py
from channels.generic.websocket import WebsocketConsumer
from django.shortcuts import get_object_or_404
import json
from django.template.loader import render_to_string
from a_rtchat.models import ChatGroup, GroupMessage
class ChatroomConsumer(WebsocketConsumer):
def connect(self):
print(self.scope)
self.user = self.scope['user']
self.chatroom_name = self.scope['url_route']['kwargs']['chatroom_name']
self.chatroom = get_object_or_404(ChatGroup, group_name=self.chatroom_name)
self.accept()
def reveive(self, text_data=None, bytes_data=None):
text_data_json = json.loads(text_data)
print(text_data_json)
body = text_data_json['message']
message = GroupMessage.objects.create(
body=body,
author = self.user,
group = self.chatroom
)
context = {
'message':message,
'user':self.user,
}
html = render_to_string("a_rtchat/partials/chat_message_p.html", context=context)
self.send(text_data=html)
asgi.py
import os
from django.core.asgi import get_asgi_application
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_core.settings')
django_asgi_app = get_asgi_application()
from a_rtchat.routing import websocket_urlpatterns
application = ProtocolTypeRouter(
{
'http':django_asgi_app,
'websocket':AllowedHostsOriginValidator(
AuthMiddlewareStack(URLRouter(
websocket_urlpatterns
)))
}
)
base.html
{% load static %}
{% load django_htmx %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Django Template">
<title>Real-time Chat</title>
<link rel="icon" type="image/x-icon" href="{% static 'favicon.ico' %}">
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://unpkg.com/htmx.org@2.0.2"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org/dist/htmx.min.js" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.2" integrity="sha384-vuKxTKv5TX/b3lLzDKP2U363sOAoRo5wSvzzc3LJsbaQRSBSS+3rKKHcOx5J8doU" crossorigin="anonymous"></script>
{% django_htmx_script %}
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.14"></script>
<script src="https://cdn.tailwindcss.com"></script>
<style type="text/tailwindcss">
[x-cloak] {
display: none !important;
}
h1 {
@apply text-4xl font-bold mb-4
}
h2 {
@apply text-xl font-bold mb-2
}
h3 {
@apply text-lg font-bold
}
p {
@apply mb-4
}
.button, button, [type='submit'], [type='button'] {
@apply bg-indigo-600 text-white font-bold rounded-lg shadow-lg transition-all cursor-pointer
}
.button, button a, [type='submit'], [type='button'] {
@apply px-6 py-4 inline-block
}
.button:hover, button:hover, [type='submit']:hover, [type='button']:hover {
@apply bg-indigo-700
}
.button:active, button:active, [type='submit']:active, [type='button']:active {
@apply scale-95
}
.button.alert, button.alert {
@apply bg-red-700
}
.button.alert:hover, button.alert:hover {
@apply bg-red-600
}
.button-red {
@apply !bg-red-500 hover:!bg-red-600
}
.button-gray {
@apply !bg-gray-300 hover:!bg-[#c3c9d0]
}
.navitems>li>a {
@apply flex items-center gap-2 h-12 px-4 hover:bg-[rgba(31,41,55,0.3)] rounded-lg;
}
.hoverlist>* {
@apply hover:bg-gray-100 rounded-md transition duration-150;
}
.hoverlist>*>a {
@apply flex items-center p-2;
}
.highlight {
@apply !bg-indigo-100;
}
.allauth content a {
@apply underline underline-offset-2
}
.allauth content a:hover {
@apply text-indigo-500
}
.allauth form[action="/accounts/signup/"] ul {
@apply hidden
}
.allauth form[action="/accounts/signup/"] ul.errorlist {
@apply block
}
.allauth .helptext {
@apply block mt-4
}
label {
@apply hidden
}
input[type=file] {
@apply bg-white pl-0
}
.textarea, textarea, input {
@apply w-full rounded-lg py-4 px-5 bg-gray-100
}
.errorlist li {
@apply p-1 pl-4 border-l-red-500 border-l-4 border-solid mb-2 text-red-500
}
label[for="id_remember"] {
@apply inline-block w-auto mr-2
}
input[name="remember"] {
@apply w-auto
}
.alert-info { @apply bg-sky-500 }
.alert-success { @apply bg-green-500 }
.alert-warning { @apply bg-red-500 }
.alert-danger { @apply bg-red-500 }
</style>
</head>
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' class="{% block class %}{% endblock %}">
{% include 'includes/messages.html' %}
{% include 'includes/header.html' %}
{% block layout %}
{% endblock %}
{% block javascript %}
{% endblock %}
</body>
</html>
I don't really understand what needs to be done, in the documentation and tutorials everything seems to be fine for everyone
You're facing a common confusion with htmx-ext-ws
and how it interacts with Django WebSockets. Let's walk through why your POST isn't working, and how to fix it.
```<form id="chat_message_form"
hx-ext="ws"
ws-connect="/ws/chatroom/public-chat"
ws-send
_="on htmx:wsAfterSend reset() me">```
- Fix the
receive
method typo:
```def receive(self, text_data=None, bytes_data=None):
...
1. **No need to worry about POST vs GET**: When using `htmx-ext-ws`, the form is serialized and sent as a JSON message — not as a POST request. That's expected behavior.