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.
tl;dr Make sure the htmx-ext-ws is properly loaded.
Looking at your previous post, you've attached js like this
<script src="https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.2" integrity="sha384-vuKxTKv5TX/b3lLzDKP2U363sOAoRo5wSvzzc3LJsbaQRSBSS+3rKKHcOx5J8doU" crossorigin="anonymous"></script>
When I try do the same, I get the following error in console:
Failed to find a valid digest in the 'integrity' attribute for resource 'https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.2' with computed SHA-384 integrity '0RHpPqZ4QLJXG/ALtieEL/G+NuAI98LYSkT9s6FgciveUhAdo/6wab5NU1tm2Bxs'. The resource has been blocked.
This probably prevents the extention js file from being loaded and that's why you're getting the GET
request as this is the default behaviour that the browser falls back to.