Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility with htmx #134

Open
aranvir opened this issue Nov 11, 2023 · 5 comments
Open

Compatibility with htmx #134

aranvir opened this issue Nov 11, 2023 · 5 comments

Comments

@aranvir
Copy link

aranvir commented Nov 11, 2023

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>
@xtlc
Copy link

xtlc commented Dec 6, 2023

have you been able to resolve this?

@aranvir
Copy link
Author

aranvir commented Dec 6, 2023

Nope, I also stopped trying.

However, I think I did test a similar setup with FastAPI and that worked out of the box. So, I assume that the issue is that the message format of htmx is different from what django-eventstream expects and I don't have the expertise to look deeper into that.

@jkarneges
Copy link
Member

At a glance I don't see anything wrong with the code. The "time" example specifies the channel the same way. It's not a format compatibility issue since the error happens before any messages are sent.

If you want to debug further you could see if this method is getting called or not and what it is returning.

@msmart
Copy link

msmart commented Feb 12, 2024

a boilderplate htmx setup worked for me with channels 3.0.5:

send_event(
        "test",
        "message",
        "<div>Content to swap into your HTML page.</div>",
        json_encode=False,
    )

I added json_code=False to ensure that the payload is just a string. Maybe you where missing the trailing slash in /events/ ?

@rexzhang
Copy link

work with uvicorn+htmx so easy, thanks this project!

urls.py

path("sse/status", include(django_eventstream.urls), {"channels": ["status"]}),

action.py

send_event("status", "message", "Check/Update is running", json_encode=False)
...
send_event("status", "message", "", json_encode=False)

_status.html

<span hx-ext="sse"
      hx-swap="innerHTML"
      sse-connect="/sse/status"
      sse-swap="message"
      class="badge rounded-pill text-bg-warning mx-1"></span>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants