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">```
  1. 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.
Вернуться на верх