django-eventstream icon indicating copy to clipboard operation
django-eventstream copied to clipboard

Compatibility with htmx

Open aranvir opened this issue 1 year ago • 5 comments

Hi, I've been trying to use django-eventstream with the htmx sse extension, but can't get it to work quite. I did look into the examples and managed to get those running so the principle environment setup should be fine. I am a beginner for both packages and also django in general so I might miss the obvious.

When I start the app and open the page I can see it connecting to the /events endpoint successfully. However, in the browser console I keep getting error message. It periodically sends another get-request to /events and while the returncode is 200 it seems like htmx does not like the response. If I manually go to /events I get::

event: stream-error
id: error
data: {"condition": "bad-request", "text": "Invalid request: No channels specified."}

No I don't know if htmx faces the same issue or if I only get this because my manual request does not specify a channel. Yet I don't even know how I would do that. I understood from the documentation that it is encouraged to use channels and it is somewhat clear how they are defined and how I can send messages to a channel but I don't understand how the client part selects the channel.

Maybe it's a htmx limitation? But even if I don't use channels, I don't get a proper reply. Would appreciate any help or tips. Or maybe I need to bring this question to the htmx github.

I appended the relevant code below:

mydemo/asgi.py

"""
ASGI config for mydemo project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
"""

import os
import django
from django.core.asgi import get_asgi_application
from django.urls import path, re_path
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import django_eventstream

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings")

application = ProtocolTypeRouter({
    'http': URLRouter([
        path('events/', AuthMiddlewareStack(
            URLRouter(django_eventstream.routing.urlpatterns)
        ), {'channels': ['test']}),
        re_path(r'', get_asgi_application()),
    ]),
})


mydemo/settings.py

...
INSTALLED_APPS = [
    'daphne',
    'channels',
    'django_eventstream',
    'server_events',
    ...
]
...
MIDDLEWARE = [
    'django_grip.GripMiddleware',
    ...
]
...
ASGI_APPLICATION = "mydemo.asgi.application"

mydemo/urls.py

from django.contrib import admin
from django.urls import path, include
from server_events import views

urlpatterns = [
    path('', include('server_events.urls')),
    path('admin/', admin.site.urls),
]

server_events/urls.py

from django.urls import path, include
import django_eventstream

from . import views

app_name = "server_events"
urlpatterns = [
    path("", views.index, name="index"),
    path("update", views.update),
    path(
        "events",
        include(django_eventstream.urls),
        {"channels": ["test"]}
    )
]

server_events/views.py

from django.shortcuts import render
from django.http import HttpResponse
from django_eventstream import send_event


# Create your views here.
def index(request):
    return render(request, 'server_events/index.html')


COUNTER = 0


def update(request):
    global COUNTER
    COUNTER += 1
    print("SENDING")
    send_event("test", "message", f"<p>Test: {COUNTER}<p>")
    return HttpResponse()

server_events/templates/server_events/index.html

<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://unpkg.com/[email protected]" integrity="sha384-rgjA7mptc2ETQqXoYC3/zJvkU7K/aP44Y+z7xQuJiVnB/422P/Ak+F/AqFR7E4Wr" crossorigin="anonymous"></script>
    <script src="https://unpkg.com/htmx.org/dist/ext/sse.js"></script>
    <link rel="stylesheet" href="https://cdn.simplecss.org/simple.css">
</head>
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
<h1>Hello World</h1>
  <button hx-get="update" hx-swap="none">Update</button>
  <div hx-ext="sse" sse-connect="/events" sse-swap="test">
      Contents of this box will be updated in real time
      with every SSE message received from the chatroom.
  </div>
</body>
</html>

aranvir avatar Nov 11 '23 12:11 aranvir