Forums

HTMX does not work on Pythonanywhere

Hello there, I am using HTMX to execute POST and GET request, everything works fine on my local on connecting to a flask server but once I deploy it on PythonAnywhere, the links do not work , has anyone faced this issue before?

Could you provide an example of your rendered HTMX?

It works. I have it in my app. You can get some tips here https://www.youtube.com/watch?v=3dyQigrEj8A But though I am using Django framework and not flask.

I think one way to tackle this problem could be to make sure that you're really linking to the HTMX .js file properly.

I'd say do a test to make sure you're really loading the file. Perhaps use their CDN in the header of your site.

This is what I am using, in my flask app here on pythonanywhere:

<script src="https://unpkg.com/htmx.org@1.5.0"></script>

In your settings.py

set as below:

STATIC_ROOT = os.path.join(BASE_DIR, "static")

STATIC_URL = "/static/"

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "/static/")
]

MEDIA_ROOT = os.path.join(BASE_DIR, "/home/myusername/myproject/myapp/media/")

MEDIA_URL = "/media/"

Then run pythonxX.Y manage.py collectstatic. pythonX.Y(e.g python3.10)

Your HTMX should work.

@acrossglobe, @totelian -- thanks for the tips! (I've only added some formatting to your posts)

I have a realtime chat app, this is just a small app for proof of concept, it won't be used by the public. I use HTMX and django for the messaging, I also use streaming. This worked perfectly fine on my local system, but extremely slow on PA. Below is my streaming function and my message sending function

class SendMessageAPIView(generics.CreateAPIView):
serializer_class = MessageSerializer
permission_classes = [permissions.IsAuthenticated]
message = None

def perform_create(self, serializer):
    other_user_id = self.kwargs["user_id"]
    try:
        receiver = User.objects.get(id=other_user_id)
    except User.DoesNotExist:
        raise NotFound(detail="The user you are trying to chat with does not exist.")


    chat = Chat.objects.filter(participants=self.request.user).filter(participants=receiver).first()

    if not chat:
        chat = Chat.objects.create()
        chat.participants.set([self.request.user, receiver])

    if self.request.content_type == 'application/json':
        message_content = self.request.data.get('content')
    else:
        message_content = self.request.POST.get('content')
    self.message = serializer.save(sender=self.request.user, receiver=receiver, content=message_content, chat=chat)


def create(self, request, *args, **kwargs):
    # return JSON to external consumer (mobile App) and html to web
    accept_header = request.headers.get('Accept', '')
    response = super().create(request, *args, **kwargs)

    if 'application/json' in accept_header:
        return response
    return render(request, "partials/message_fragment.html", {"message": self.message})

def message_stream(request, user_id): def event_stream(): last_timestamp = timezone.now() timeout = 240
last_activity = timezone.now()

    while True:
        new_messages = Message.objects.filter(
            sender__in=[request.user, user_id],
            receiver__in=[request.user, user_id],
            timestamp__gt=last_timestamp
        ).order_by("timestamp")

        if new_messages:
            last_timestamp = new_messages[len(new_messages) - 1].timestamp

            received_messages = new_messages.filter(
                sender=user_id,
                receiver=request.user
            )

            for latest_message in received_messages:
                formatted_time = latest_message.timestamp.strftime("%B %d, %Y, %I:%M %p").replace(" 0", " ").lower().replace("am", "a.m.").replace("pm", "p.m.")
                html_message = (
                    f'<div class="message received">'
                    f'<div class="content">'
                    f'<div class="text">{latest_message.content}</div>'
                    f'<div class="timestamp">{formatted_time}</div>'
                    f'</div>'
                    f'</div>'
                )
                yield f"event: message\ndata: {html_message}\n\n"


            # Update last activity time
            last_activity = timezone.now()
        else:
            if (timezone.now() - last_activity).total_seconds() > timeout:

                close_message = (
                    f'<div class="text-danger" style="text-align: center;">'
                    f'<div class="text">Connection closed due to inactivity, Refresh the page to continue</div>'
                    f'</div>'
                )

                yield f"event: close\ndata: {close_message}\n\n"
                break

        time.sleep(2)

response_server = StreamingHttpResponse(event_stream(), content_type='text/event-stream')
response_server['Cache-Control'] = 'no-cache'  
response_server["X-Accel-Buffering"] = "no" 
return response_server

What part is slow? Do you see anything in your logs?

Nothing in my error log, maybe server log

2025-03-11 08:38:57 announcing my loyalty to the Emperor...
2025-03-11 08:38:57 Tue Mar 11 08:38:57 2025 - SIGPIPE: writing to a closed pipe/socket/fd (probably the client disconnected) on request /chat/1/stream/ (ip 10.0.5.105) !!!
2025-03-11 08:38:57 Tue Mar 11 08:38:57 2025 - uwsgi_response_writev_headers_and_body_do(): Broken pipe [core/writer.c line 306] during GET /chat/1/stream/ (10.0.5.105)
2025-03-11 08:38:57 announcing my loyalty to the Emperor...
2025-03-11 08:38:57 Tue Mar 11 08:38:57 2025 - SIGPIPE: writing to a closed pipe/socket/fd (probably the client disconnected) on request / (ip 10.0.5.105) !!!
2025-03-11 08:38:57 Tue Mar 11 08:38:57 2025 - uwsgi_response_writev_headers_and_body_do(): Broken pipe [core/writer.c line 306] during GET / (10.0.5.105)

[edit by admin: formatting]

I think I can see the problem -- you're using Django in async mode, which isn't supported by our normal web-serving infrastructure. We have a new experimental system that it should work with, however -- but you'd need to delete your existing website (just the config on the "Web" page, not the code) and re-create it with our command-line tools. If you'd like to give that a go, see the Django instructions on this help page.