Code Monkey home page Code Monkey logo

turbo-django's Introduction

Build Status Documentation Status Issues Twitter

Unmaintained // Turbo for Django

Warning

This library is unmaintained. Integrating Hotwire and Django is so easy that you are probably better served by writing a little bit of Python in your code than using a full-blown library that adds another level of abstraction. It also seems that the Django community is leaning more towards HTMX than Hotwire so you might want to look over there if you want more "support" (but we still think that Hotwire is very well suited to be used with Django)

Integrate Hotwire Turbo with Django with ease.

Requirements

  • Python 3.8+
  • Django 3.1+
  • Channels 3.0+ (Optional for Turbo Frames, but needed for Turbo Stream support)

Installation

Turbo Django is available on PyPI - to install it, just run:

pip install turbo-django

Add turbo and channels to INSTALLED_APPS, and copy the following CHANNEL_LAYERS setting:

INSTALLED_APPS = [
    ...
    'turbo',
    'channels'
    ...
]

CHANNEL_LAYERS = {
    "default": {
        # You will need to `pip install channels_redis` and configure a redis instance.
        # Using InMemoryChannelLayer will not work as the memory is not shared between threads.
        # See https://channels.readthedocs.io/en/latest/topics/channel_layers.html
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("127.0.0.1", 6379)],
        },
    }
}

And collect static files if the development server is not hosting them:

./manage.py collectstatic

Note: Both Hotwire and this library are still in beta development and may introduce breaking API changes between releases. It is advised to pin the library to a specific version during install.

Quickstart

Want to see Hotwire in action? Here's a simple broadcast that can be setup in less than a minute.

The basics:

  • A Turbo Stream class is declared in python.

  • A template subscribes to the Turbo Stream.

  • HTML is be pushed to all subscribed pages which replaces the content of specified HTML p tag.

Example

First, in a django app called quickstart, declare BroadcastStream in a file named streams.py.

# streams.py

import turbo

class BroadcastStream(turbo.Stream):
    pass

Then, create a template that subscribes to the stream.

from django.urls import path
from django.views.generic import TemplateView

urlpatterns = [
    path('quickstart/', TemplateView.as_view(template_name='broadcast_example.html'))
]
# broadcast_example.html

{% load turbo_streams %}
<!DOCTYPE html>
<html lang="en">
<head>
    {% include "turbo/head.html" %}
</head>
<body>
    {% turbo_subscribe 'quickstart:BroadcastStream' %}

    <p id="broadcast_box">Placeholder for broadcast</p>
</body>
</html>

Now run ./manage.py shell. Import the Turbo Stream and tell the stream to take the current timestamp and update the element with id broadcast_box on all subscribed pages.

from quickstart.streams import BroadcastStream
from datetime import datetime

BroadcastStream().update(text=f"The date and time is now: {datetime.now()}", id="broadcast_box")

With the quickstart/ path open in a browser window, watch as the broadcast pushes messages to the page.

Now change .update() to .append() and resend the broadcast a few times. Notice you do not have to reload the page to get this modified behavior.

Excited to learn more? Be sure to walk through the tutorial and read more about what Turbo can do for you.

Documentation

Read the full documentation at readthedocs.io.

Contribute

Discussions about a Django/Hotwire integration are happening on the Hotwire forum. And on Slack, which you can join by clicking here!

As this new magic is discovered, you can expect to see a few repositories with experiments and demos appear in @hotwire-django. If you too are experimenting, we encourage you to ask for write access to the GitHub organization and to publish your work in a @hotwire-django repository.

License

Turbo-Django is released under the MIT License to keep compatibility with the Hotwire project.

If you submit a pull request. Remember to add yourself to CONTRIBUTORS.md!

turbo-django's People

Contributors

blopker avatar c4ptaincrunch avatar camilonova avatar davish avatar epineda avatar joirichi avatar julianfeinauer avatar nerdoc avatar paoloromolini avatar scuml avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

turbo-django's Issues

render() requries context (Components require documentation)

In #53 (I didn't find another documentation of Components) you write at the end: cart_component.render() - but the render function has a mandatory context parameter.

So in my tests I could not get this to work...

Is there any documentation for Components (which are GREAT!)?

user_passes_test signature?

in the ModelStream example at the end, there is the user_passes_test method:

def user_passes_test(self, user, object_id):
    return True

This signature does not match the parent's signature:

def user_passes_test(self, user):
    pass

Could it be that you forgot ,*args, **kwargs in ModelStream? Or is the object_id obsolete?

Any further development?

Hello devs,
no changes since last year, development seems to have stagnated.
Has this library any future, or will it be abandoned?

Fresh install issues

Hi Everyone!

Been figuring out if I should go with Hotwire or htmx/alpine and wanted to get something running to then explore how things work as I'm pretty new to coding so abstractions are a bit tricky.

Followed the instructions to get the chat experiment running and it runs, and unfortunately I've run into a ValueError:
`[18/Nov/2022 18:53:13] "GET /ws/ HTTP/1.1" 500 131700
Internal Server Error: /ws/
Traceback (most recent call last):
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\fields_init_.py", line 1823, in get_prep_value
return int(value)
ValueError: invalid literal for int() with base 10: 'ws'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
response = get_response(request)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\views\generic\base.py", line 70, in view
return self.dispatch(request, *args, **kwargs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
return handler(request, *args, **kwargs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\views\generic\detail.py", line 106, in get
self.object = self.get_object()
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\views\generic\detail.py", line 36, in get_object
queryset = queryset.filter(pk=pk)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\query.py", line 941, in filter
return self._filter_or_exclude(False, args, kwargs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\query.py", line 961, in _filter_or_exclude
clone._filter_or_exclude_inplace(negate, args, kwargs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\query.py", line 968, in _filter_or_exclude_inplace
self._query.add_q(Q(*args, **kwargs))
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\sql\query.py", line 1416, in add_q
clause, _ = self.add_q(q_object, self.used_aliases)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\sql\query.py", line 1435, in add_q
child_clause, needed_inner = self.build_filter(
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\sql\query.py", line 1370, in build_filter
condition = self.build_lookup(lookups, col, value)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\sql\query.py", line 1216, in build_lookup
lookup = lookup_class(lhs, rhs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\lookups.py", line 25, in init
self.rhs = self.get_prep_lookup()
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\lookups.py", line 77, in get_prep_lookup
return self.lhs.output_field.get_prep_value(self.rhs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\fields_init
.py", line 1825, in get_prep_value
raise e.class(
ValueError: Field 'id' expected a number but got 'ws'.
[18/Nov/2022 18:53:24] "GET /ws/ HTTP/1.1" 500 131700
Internal Server Error: /ws/
Traceback (most recent call last):
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\fields_init
.py", line 1823, in get_prep_value
return int(value)
ValueError: invalid literal for int() with base 10: 'ws'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
response = get_response(request)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\views\generic\base.py", line 70, in view
return self.dispatch(request, *args, **kwargs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
return handler(request, *args, **kwargs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\views\generic\detail.py", line 106, in get
self.object = self.get_object()
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\views\generic\detail.py", line 36, in get_object
queryset = queryset.filter(pk=pk)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\query.py", line 941, in filter
return self._filter_or_exclude(False, args, kwargs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\query.py", line 961, in _filter_or_exclude
clone._filter_or_exclude_inplace(negate, args, kwargs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\query.py", line 968, in _filter_or_exclude_inplace
self._query.add_q(Q(*args, **kwargs))
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\sql\query.py", line 1416, in add_q
clause, _ = self._add_q(q_object, self.used_aliases)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\sql\query.py", line 1435, in add_q
child_clause, needed_inner = self.build_filter(
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\sql\query.py", line 1370, in build_filter
condition = self.build_lookup(lookups, col, value)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\sql\query.py", line 1216, in build_lookup
lookup = lookup_class(lhs, rhs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\lookups.py", line 25, in init
self.rhs = self.get_prep_lookup()
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\lookups.py", line 77, in get_prep_lookup
return self.lhs.output_field.get_prep_value(self.rhs)
File "C:\Users\rob_s\PycharmProjects\Autobiography\venv\lib\site-packages\django\db\models\fields_init
.py", line 1825, in get_prep_value
raise e.class(
ValueError: Field 'id' expected a number but got 'ws'.
[18/Nov/2022 18:53:35] "GET /ws/ HTTP/1.1" 500 131700
`

I've tried searching the code base for ws to see where it was coming from but no luck, any pointers with this?

Also, I'm not sure if this is related to this issue, or if I haven't understood what the demo was supposed to show, but when I submit a new message in the chat, it requires a full page reload.

My (perhaps mis)understanding was that turbo enables real time DOM manipulation without reloading, is this correct or have I got the wrong idea?

ModelStream as Mixin?

I do not get the full purposes of ModelStream and Component, docs are not fully comprehensive for me here. If I understand correctly, you can add a Meta class to any Stream (even Component?) with a model attribute, and Turbo will connect pre_save/_delete etc. signals to that model.

Then why do you have a ModelStream class to inherit from?

Wouldn't it be possible to implement ModelStream as Mixin? So you can add it to a Component too?

Or am I thinking wrong here?

Update specific instance instead of the entire container with dom_target

Hey guys! First of all thank you for the work you've done on this implementation, it's a great starting point to implementing turbo with django and I think It'll only get better.

I have a question concerning the dom_target that generated in get_dom_target in BroadcastableMixin. I have recreated the simple chat application, this is my model setup:

class Room(BroadcastableMixin, models.Model):
    name = models.CharField(max_length=50)

    def get_absolute_url(self):
        return reverse_lazy('chat:room_detail', kwargs={'name': self.name})

    def __str__(self):
        return self.name


class Message(BroadcastableMixin, models.Model):
    broadcasts_to = ['room']
    broadcast_self = False

    room = models.ForeignKey(Room, on_delete=models.CASCADE, related_name='messages')
    content = models.TextField()
    posted_at = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return self.content

And these are the templates:

# room.html
{% extends "base.html" %}
{% load turbo_streams %}

{% block content %}
    {% turbo_stream_from room %}
    <h3>Room {{ room.name }}:</h3>

    <div id="messages">
        {% for message in room.messages.all %}
            {% include "chat/message.html" %}
        {% endfor %}
    </div>

    <hr>
    <form method="POST">
        {% csrf_token %}
        {{ message_form.as_p }}
        <button type="submit">Add Message</button>
    </form>
{% endblock %}

# message.html
{% load humanize turbo_streams %}
<p id="{% stream_id message %}">{{ message.posted_at|naturaltime }}: {{ message }} (#{{message.pk}})</p>

The problem I am encountering is, whenever a message changes, there's a signal sent with dom_target pointing at messages. I don't think that should be the case, I'd like to target. specific message_<pk> to avoid re-rendering all the messages (which btw don't render with that setup, all my messages get replaced with just a single one - the updated one).

An example signal that gets sent, as you see dom_target is messages not message_9:

{'signed_channel_name': 'broadcast-chat.room-1:xPgfqh9WzMQHO_y8OdfE7-0H1ePopB0vfWRZIWtg1hQ', 'data': '<turbo-stream action="replace" target="messages">\n  <template>\n      \n          \n<p id="message_9">a minute ago: Welp Welp (#9)</p>\n      \n  </template>\n</turbo-stream>'}

I think get_dom_target should by default return f"{self._meta.verbose_name.lower()}_{self.pk}", I don't really see how messages is helpful. Am I missing something or is this some sort of a bug? If you need more I could share my playground repo with you.

Quickstart not working

High level: following Quickstart did not result in a working example. With 1 shell running Django web server and one running a Django shell, I was not able to produce expected functionality. It seems that channel broadcasts were lost in the ether. This could be a missing settings, but then again, I would expect to see an error if that were the case.

Info:

// requirements.txt
django==3.1.13
uvicorn[standard]==0.15.0
turbo-django==0.2.4
websockets==10.0
channels==3.0.4
....

OS: OSx 10.15.7 (Catalina)

Please let me know how I can assist.

Docs should be better.

I can't get turbo_django (Model)streams to work. Components work, but my ModelStream refuses doing what I want. I suppose there are some errors in the documentation, and at least it should be improved here.

  1. First, at templates.rst, you say room.channel.append(... - room in this case (a ModelStream instance) has no "channel" attr. Maybe you mean "stream" here? But there are other things that are missing. "..., or pass a Django instance..." is a bit weird. I think you mean a Model instance that has a stream attached?
  2. RoomListChannel isn't anything that can be referred to in the tutorial before, and is never explained. Is this a stream?
  3. The turbo_subscribe tag section should be before the usage of it.

If you help me a bit to understand I could try to reorganize and corect this page for "newcomers" like me.
But I need help - because, like I said, using the docs, I can't get ModelStream to work...

Add table example to documentation

More examples are needed to demonstrate the ease & power of this library.

Create a paginated table view with sort and filter capabilities using turbo-frames returned from POST requests. To keep things simple from an example standpoint, do not use websockets/turbo-streams.

How to decide what to implement?

I'm wondering what a good way to move forward is. Should we add utilities as people's projects seem to need them? Should we look at what's offered in hotwired/turbo-rails right now and port it over to Python? The second seems to make sense to me, but I personally don't have any experience with Ruby and have had trouble in the past reading through Rails codebases. If anyone else has experience in that area then I'd love to work with them!

How to approach testing?

I'm wondering how to approach testing for the utilities we're building? Probably most can be unit-tested, but it's probably also a good idea to make sure they work as intended with Turbo. We could set up integration tests with Cypress?

add button click action example to documentation

like #13, it would be extremely helpful to add some more examples to the documentation, e.g. How to implement a simple <button> that does something in the backend and returns HTML. ATM there is only the <turbo-frame> with a form.

A great example would be adding a "delete" button to the messages, with an id etc.

Is this even possible without a form with turbo-django?

Turbo Components

Explore common use cases of turbo and see if generic components can be created from these to reduce cognitive load of linking url -> view -> template -> turbo-frame -> turbo-stream. If some, or all of these concepts can be bundled together, if only occasionally, it will help newcomers and veterans alike.

The simplest use-case would be a BroadcastComponent.

<!-- sample_broadcast.html -->
<div class="{{alert_class}}">{{alert_content}}</div>
# components.py
class AlertBroadcastComponent(BroadcastComponent):
    template_name = "sample_broadcast.html"

# views.py
def index_view(request):
   alert_broadcast = AlertBroadcastComponent()
   return render(request, "index.html", {'alert_broadcast': alert_broadcast})

def an_async_method():
    alert_component = AlertBroadcastComponent()
    alert_component.render({
        'alert_class': 'warning',
        'alert_content': 'Server will be restarted in 60 seconds.',
    })
<!-- index.html -->
{{alert_broadcast}}
or perhaps bypass context and use
{% turbo_component "app_name:AlertBroadcastComponent" %}

User specific example

To demonstrate a slightly more complex use-case, let's sketch out how this might work for a counter for items in a user's shopping cart. For this, we want a template to be streamed only to a specific user. All the user-specific streams are created as needed.

<!-- cart_counter.html -->
<div class="badge">{{cart.item_count}}</div>
# components.py
class CartCounterComponent(UserBroadcastComponent):
    template_name = "cart_counter.html"


# views.py
def header(request):
   cart_counter = CartCounterComponent(user=request.user)
   return render(request, "index.html", {'cart_counter': cart_counter})

def add_item_to_cart(request, item, quantity):
    cart, is_new = Cart.objects.get_or_createuser=request.user)
    cart.add_item(item, quantity)
    
    cart_component = CartCounterComponent(user=request.user)
    cart_component.render({'cart': cart})
<!-- index.html -->
{{cart_counter}}
or perhaps bypass context and use
{% turbo_component "app_name:CartCounterComponent" request.user %}

UUID as a primary key

Hey, it might be something that can be easily solved, but my initial approach to turbo-django is stoped by "Object of type UUID is not JSON serializable" error.

Error:
site-packages/turbo/templates/turbo/turbo_stream_source.html, error at line 2
<turbo-channels-stream-source signed-channel-name="**{{ stream.signed_stream_name }}**" args="{{ stream.get_init_args_json }}" kwargs="{{ stream.get_init_kwargs_json }}"></turbo-channels-stream-source>

site-packages/django/core/signing.py, line 117, in dumps
data = serializer().dumps(obj)

Model:
class MyModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

howto compile docs

I tried to add some hints in the docs - but I want to compile the sphinx/docs locally first.
But when I go into the doc dir, start automake.sh and change something, I get the error:

WARNING: autodoc: failed to import class 'components.BroadcastComponent' from module 'turbo'; the following exception was raised:
Traceback (most recent call last):
  File "/.../turbo-django/.venv/lib/python3.10/site-packages/sphinx/ext/autodoc/importer.py", line 62, in import_module
    return importlib.import_module(modname)
[...]
  File "/.../turbo-django/.venv/lib/python3.10/site-packages/django/conf/__init__.py", line 63, in _setup
    raise ImproperlyConfigured(
django.core.exceptions.ImproperlyConfigured: Requested setting SECRET_KEY, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.
[...]

This seems to me that the docs try to import Django. One solution is to (fake) import django in conf.py - but I don't know how you do that in your environment.
@scuml could you provide some hints how to do that?

If you want I can add a section for testing as well then - pytest runs fine here localy for the whole suite.

"nested" Streams app name breaks turbo?

I am evaluating turbo-django for a project, am struggling myself throught the tutorial and creating some of the ideas in my project.

I added a (empty) Stream class named PrescriptionRequestStream, and included {% turbo_subscribe 'prescriptions:PrescriptionRequestStream' %} into my template.

Now Django complains:

TemplateSyntaxError at /dashboard/requests

Could not fetch stream with name: 'prescriptions:PrescriptionRequestStream'  
Registered streams: ['medux_online.plugins.prescriptions:PrescriptionRequestStream']

It's a very brilliant idea to list the available streams in the error message, cool to debug. BUT: I copy'n'pasted medux_online.plugins.prescriptions:PrescriptionRequestStream into the templatetag. And I get the same error for that:

Could not fetch stream with name: 'medux_online.plugins.prescriptions:PrescriptionRequestStream'  
Registered streams: ['medux_online.plugins.prescriptions:PrescriptionRequestStream']

According to stream_for_stream_name(), there is a hint that it should be >>> stream_for_stream_name("app.RegularStream") - meaning the namespace before the class is the app, and it is not a dotted path.

But then the app name generation in Turbo is done wrong when apps are not in the first level of Django's directory tree.

The problem seems to be in the autodiscover_streams() method:
app_name = broadcast_module.__package__ uses a dotted path as the app name.

I replaced it with app_name = broadcast_module.__package__.split(".")[-1]and it worked instantly.

This may not be the best approach, as it's just a first glance into the code of turbo-django - maybe you have a better idea and the bigger picture.

Custom turbo-channels-stream-source element can't be self closing

Hello! Thanks everyone for putting this library together.

This isn't major, but I believe that you can't have self closing custom elements (https://developers.google.com/web/fundamentals/web-components/customelements#define) so turbo_stream_source.html should be:
<turbo-channels-stream-source signed-channel-name="{{ channel_name }}"></turbo-channels-stream-source>
Currently the custom element subsumes the other elements below it, which hasn't broken anything so far for me, but I don't think it's the intended behavior.

HTMX-like actions for turbo-django

This is just a question/idea. I came across many frameworks in the last year, from sockpuppet to Unicorn and HTMX/Django.
one of the things all of them offer is that actions like triggering responses can be started from any HTML element, not just by forms/buttons and anchors. E.g. changing the value of a checkbox could trigger a frame reload. This is not conveniently possible with turbo-django - however, it could be done IMHO with stimulus - or something like alpine.js.

Is there any chance that this will be possible with turbo-django?

component subscription calls without "user"

The AlertBroadcastComponent example works well, but when I change to the UserBroadcastComponent, the example starts as normal, the webpage renders, but after a few seconds, I get an error:

User `None` does not have permission to access stream 'prescriptions:AlertBroadcastComponent'.

I tried to dig into that, but could not find the culprit... But I think this is a bug.

More than one Stream per Model?

Could turbo be refactored to add more than one Stream per Model?
It sometimes is needed to add more streams to a model to add different functionality (of different plugins e.g.). If there is already a model stream, it "blocks" adding another one.
So plugin functionality has to be added into the ModelStream.

This IMHO could be done better by making it possible to add more than one Stream to a Model, and e.g. making .stream a list object.

Is this possible?

Improve Quickstart by adding requisite steps

When running the example I ran into several configuration errors due to not understanding that I must read Tutorial: Part 1 first. It does not specify to go through the tutorial set up first, leaving several settings missing.

I believe Quickstart can be improved by adding two steps.

  1. pip install django turbo-django channels
  2. Add turbo and channels to INSTALLED_APPS.
    Change WSGI_APPLICATION = 'turbotutorial.wsgi.application' to ASGI_APPLICATION = 'turbotutorial.asgi.application'
ASGI_APPLICATION = 'turbotutorial.asgi.application'


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'turbo',
    'channels',
]

CHANNEL_LAYERS = {
    "default": {
        # Don't use this backend in production
        # See https://channels.readthedocs.io/en/latest/topics/channel_layers.html
        "BACKEND": "channels.layers.InMemoryChannelLayer"
    }
}

Proposal: `turbo_frame` tag.

Hey team, I'm pretty excited about the prospect of writing less JavaScript and so, am pretty excited about this project.

After wrapping my head around the Rails version of the frames tag (https://github.com/hotwired/turbo-rails/blob/main/app/helpers/turbo/frames_helper.rb), I've come up with what I think is the equivalent in Django land. Let me know what you think!

Simplest version

This renders a Django template inline, passing the parent template context to the child template like an include tag would. The tag would wrap the child context with <turbo-frame>, like the Rails version does. This simple version would auto-generate a frame ID based on this template name, in this case myapp-mytemplate. It could also add that ID to the parent context (turbo_id) as it passes the context to the child template.

Tag:

{% turbo_frame 'myapp/mytemplate.html' %}

Template:

<div>My frame with ID {{turbo_id}}!</div>

Output:

<turbo-frame id="myapp-mytemplate">  
<div>My frame with ID myapp-mytemplate!</div>
</turbo-frame>

Tag with arguments

This demonstrates overriding default behavior to the previous tag. Note all arguments are prefixed with turbo to avoid collisions in the lazy tags below.
turbo_target: Custom target. Optional.
turbo_id: Custom frame ID. Works for both lazy and non-lazy frames. Optional.

Tag:

{% turbo_frame 'myapp/mytemplate.html' turbo_target='_top' turbo_id='my_id' %}

Template:

<div>My frame with ID {{turbo_id}}!</div>

Output:

<turbo-frame id="my_id" target="_top">  
<div>My frame with ID my_id!</div>
</turbo-frame>

Lazy frame

This demonstrates using the tag with lazy loading. The first argument is still a template path, but this will be replaced once the remote content loads. In a lazy frame the template argument is also optional. turbo_src is either a URL or a named URL and makes the frame "lazy". The idea is to be similar to how the url tag works. Unknown arguments are passed to URL resolver, just like with the builtin url tag. Arguments prefixed with turbo are used to configure the frame. This adds a new option, turbo_loader, that only works with lazy frames. The previous options work here too. For lazy frames the auto generated ID is based on turbo_src and the arguments passed.
turbo_src: Named URL or URL. Turns the frame into a lazy frame. Optional.

Tag:

{% turbo_frame 'my_spinner.html' turbo_src='myview' arg1=v1 arg2=v2 %}

Template:

<div>Loading {{turbo_id}}!</div>

Output:

<turbo-frame id="myview-v1-v2" src="https://myapp.com/myurl/1/2">  
<div>Loading myview-v1-v2!</div>
</turbo-frame>

Generate HTML from JS before sending it over the wire

Hi Everyone,

Is it possible to run javascript to create html before sending it through a stream?
or if this isn't,
Is it possible to run javascript which arrives into the client via a stream?


Currently it seems that any JS which is sent over via a stream just comes through as the plain text, without being ran before sending (example 1) or after arrival (example 2).

Example 1 - Not running JS before
message.html
`

 {% for message in room.messages.all %}
        {id: {{message.id}}, content: '{{message.text}}', start: '{{message.created_at.date}}'},
{% endfor %}

<div id="visualization"></div>
    <script type="text/javascript">

      (function (room, vis_id) {

        // DOM element where the Timeline will be attached
        var container = document.getElementById(vis_id);

        // Create a DataSet (allows two way data-binding)
        var items = new vis.DataSet([
                    {% for message in room.messages.all %}
                        {id: {{message.id}}, content: '{{message.text}}', start: '{{message.created_at.date}}'},
                    {% endfor %}
                  ]);

        // Configuration for the Timeline
        var options = {};

        // Create a Timeline
        var timeline = new vis.Timeline(container, items, options)

        })();
    </script>
`

Comes out in the browser as

`

{id: 85, content: 'Test Message', start: 'Dec. 1, 2022'},
{id: 86, content: 'asd', start: 'Dec. 1, 2022'},
       

<div id="visualization"></div>

<script type="text/javascript">

      (function (room) {

        // DOM element where the Timeline will be attached
        var container = document.getElementById(visualization);

        // Create a DataSet (allows two way data-binding)
        var items = new vis.DataSet([                        

                        {id: 85, content: 'Test Message', start: 'Dec. 1, 2022'},
                    
                        {id: 86, content: 'asd', start: 'Dec. 1, 2022'},
                    
                  ]);

        // Configuration for the Timeline
        var options = {};

        // Create a Timeline
        var timeline = new vis.Timeline(container, items, options)

        })();
    </script>
` As can be seen, the stream is functioning as expected for the django information, however otherwise the html otherwise just copied across, without populating the related div.

The exact same code is used to load the visualisation on the initial page load, and works correctly.

Example 2 - Not running JS after
template.html
`

        {% for message in room.messages.all %}
            {id: {{message.id}}, content: '{{message.text}}', start: '{{message.created_at.date}}'},
        {% endfor %}

    <script type="text/javascript">
          console.log('Test');
    </script>
`

Comes out in the browser as

`

{id: 85, content: 'Test Message', start: 'Dec. 1, 2022'},
{id: 86, content: 'asd', start: 'Dec. 1, 2022'},
        
<div id="visualization"></div>

<script type="text/javascript">
       console.log('Test');
</script>
` with nothing in the console.

I've tried several different things with none of them working, but may have made mistakes when attempting them:

  1. Putting the Javascript in different places, for example in a django variable which can then be called - I imagine this didn't work due to potential security issues of being able to run potentially unwanted JS.
  2. Editing turbo to use render() rather than render_to_string() in order to include the content_type variable in the template, and then trying to render a JS file with the appropriate mime type.
  3. Loading the page with selenium, copying the resulting html into a file, then loading that as a template (not a scalable option, but was simply testing it).
  4. Running javascript in python using js2py, which doesnt work as the construction of the visualisation requires the use of document.getElementById, which I haven't been able to figure out how to connect it to a DOM.
  5. I've tried using the turbo.TurboRender.response() as described here
  6. I've tried using the FrameElement.reload() function - though didn't seem to have any luck getting it working at all, so my use may be off

Breaking it down, I think it would be possible to do if I was able to import the existing DOM, or a constructed one with something like dominate, into a javascript instance or reload the frame using the javascript

Any advice or pointers would be greatly appreciated!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.