Welcome to turbo-django’s documentation!¶
This package provides helpers for server-side rendering of Hotwired/Turbo websocket based Streams.
Disclaimer: the Hotwired/Turbo client libraries are, at time of writing, still in Beta. We expect there will be breaking changes until the first stable release. This package, and the Turbo client, should therefore be used with caution in a production environment. The version used in testing is @hotwired/turbo==7.0.0-beta.2.
Requirements¶
This library is tested for Python 3.6+.
Turbo for Django¶
This repository aims to provide utilities for working with Turbo with the Django web framework.
The first step for setup is to include Turbo in the <head>
for
your templates. Simply put {% include 'turbo/head.html' %}
somewhere
in your head,
which will include the Turbo libraries we need in order to use Turbo
Drive, Frames and Streams in our app.
Currently, the repository contains utilities for working with Turbo Streams over Websockets, the one part of Turbo which requires a specific integration with the backend framework to function. In Django’s case, this means taking advantage of Django Channels and ASGI support.
The Django Streams integration is heavily inspired by the helpers and mixins found in turbo-rails. There are a few steps required to get the integration working.
After setting up Channels according to the
documentation,
make sure to modify the top-level ProtocolTypeRouter
to include
the TurboStreamsConsumer
:
from channels.routing import ProtocolTypeRouter
from turbo.consumers import TurboStreamsConsumer
application = ProtocolTypeRouter({
"http": AsgiHandler(),
"websocket": TurboStreamsConsumer.as_asgi() # Leave off .as_asgi() if using Channels 2.x
})
Throughout these docs we’ll be using these two basic models for a chat app, similar to the Hotwire demo video and the Rails demo app:
# chat/models.py
from django.db import models
class Room(models.Model):
name = models.CharField(max_length=255)
class Message(models.Model):
room = models.ForeignKey(Room, related_name="messages", on_delete=models.CASCADE)
text = models.TextField()
turbo-django
provides a BroadcastableMixin
that enables CRUD
operations from a given model
to be broadcast over websockets. To enable messages to be broadcast
over websocket to users
listening in on a particular room, we add the BroadcastableMixin
and tell turbo-django
that actions on messages should be broadcast to the related Room
:
class Message(BroadcastableMixin, models.Model):
broadcast_to = ["room"]
broadcast_self = False
room = models.ForeignKey(Room, related_name="messages", on_delete=models.CASCADE)
text = models.TextField()
broadcast_to
can contain any foreign key field on the model. We
set broadcast_self
to False
since we don’t
anticipate listening in to updates on a single message, only on the
associated Room
. This is all the set-up on the
model side of things that we’ll need!
Next, we can declare that a given page should subscribe to broadcasts
for a given model instance. Make sure to
{% load turbo_streams %}
to get access to the template tag in your
template.
Make sure to pass the model instance (here, it’s passed as room
)
into the template context. Somewhere in the <body>
,
we need to add the template tag {% turbo_stream_from room %}
.
That’s all we need to do to connect this template, when
rendered on the front-end, to the stream of updates for a given model
instance!
Streams need to render HTML of updates on the backend using template
partials to send over to the frontend over the wire.
turbo-django
at the moment relies on some conventional names for
templates and IDs:
app_name/model_name.html
should contain the template partial for rendering the model. The context passed in will have the labelsobject
as well asmodel_name
refer to the instance being rendered. Streams mandates that there’s one, top-level element wrapping the HTML. You can make sure the element’s ID is set properly by using the templatetag{% stream_id object %}
A consistent element ID is necessary to ensure that the Streams CRUD operations, titledreplace
, can predictably find elements on the page.For our example with the
Message
model, we should create a template calledchat/message.html
, where the top-level HTML element should have the IDid={% stream_id message %}
.The page template (the one that contains the
turbo_stream_from
tag) should put the related instances into some sort of list, where theid
of the list is the lower-cased plural name of the model. This is necessary for Streams’s insert actionsappend
andprepend
, which need a parent container to insert elements into.In our example, there should be a
div
orul
containing messages, with the encompassing element’sid
beingmessages
.