An early stage integration of Hotwire Turbo with Django

Overview

Build Status Documentation Status Issues Twitter

Note: This is not ready for production. APIs likely to change dramatically. Please drop by our Slack channel to discuss!

Turbo for Django

Requirements

  • Python 3.6+
  • Django 3.1+
  • Channels 3.0+ (Optional for Turbo Stream support)

This repository aims to help you integrate Hotwire Turbo with Django. Inspiration taken from @hotwired/turbo-rails. Documentation can be found here for the current integration.

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

As we discover this new magic, 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 a write access to the GitHub organization and to publish your work in a @hotwire-django repository.

We expect to gain knowledge and experience with Hotwire over time and will try to extract useful code from the demos and package it in self contained "pip-installable" packages: turbo-django and stimulus-django.

Structure

The turbo directory contains the package with helpers, templatetags and utilities for integrating Turbo tightly into Django. Currently, it contains a Broadcastable mixin and a Django Channels websocket consumer to allow for realtime updates with Turbo Streams.

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!

Comments
  • How to decide what to implement?

    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!

    discussion 
    opened by davish 10
  • Fresh install issues

    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?

    opened by RobSisson 7
  • component subscription calls without

    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.

    opened by nerdoc 7
  • Refactoring to unlink Streams from Models

    Refactoring to unlink Streams from Models

    Solves #14

    This is not yet fully polished but I wanted to hear Feedback from others like @davish and @blopker about these changes.

    Basically, I subclassed BroadcastableMixin with a BroadcastableModelMixin which contains the Model spezific logic and migrated all model-specific tasks from Consumers notify method there. So now we should be able to send general Messages with the consumer.

    As Demo I added the wiretap view ("/wiretap") into the Chat Demo which receives all Messages send in all Channels.

    What do you think?

    opened by JulianFeinauer 7
  • Quickstart not working

    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.

    opened by elamje 5
  • A few stream consumer refactors

    A few stream consumer refactors

    I noticed that when navigating to different pages with a stream open, we'd get multiple stream responses like the unsubscribe event wasn't working. This is an attempt to fix that.

    opened by blopker 4
  • Vendor JS Libs

    Vendor JS Libs

    This PR vendors the JS libraries. This is useful so users of this package don't have to rely on third party CDNs which is against many companies' security policies and adds another failure point to sites. If people want to use other versions of the vendored libs or want to create their own JS bundle, it should be easy enough to just not include the head.html template and use their own solution.

    List of changes:

    • Changed chat app setup instructions to use Python 3's built in venv module so we don't rely on the virtualvenv package being installed globally.
    • Added some seed data to the chat app so people can get set up easier.
    • Removed channels from install_requires so people using WSGI don't need to have it installed. We'll want to mention something about installing it in the Streams install instructions.
    • Moved the previously inline JS into its own JS file, then wrapped it in IIFE so variables don't leak. We can minify this at some point, but it's so small now it didn't seem worth it.
    • Changed turbo.js to load the es2017 version as our custom JS wont work on es5 anyway.
    • Changed reconnecting-websocket to the IIFE version minified which is smaller and avoids an exception with the CJS version.
    • Used the static tag with data-turbo-track="reload" so the JS files get fingerprinted and Turbo reloads itself when those resources change in production.
    • Added defer to script tags so they don't block rendering the rest of the page.
    • Dynamically create the websocket URL to work in other environments besides localhost, also now supports secure connections.
    opened by blopker 4
  • render() requries context (Components require documentation)

    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!)?

    opened by nerdoc 2
  • "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.

    opened by nerdoc 2
  • Improve Quickstart by adding requisite steps

    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"
        }
    }
    
    opened by elamje 2
  • UUID as a primary key

    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)

    opened by meesix 1
  • Generate HTML from JS before sending it over the wire

    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!

    opened by RobSisson 0
  • Docs should be better.

    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...

    opened by nerdoc 2
  • HTMX-like actions for turbo-django

    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?

    opened by nerdoc 1
  • howto compile docs

    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.

    opened by nerdoc 0
  • add button click action example to documentation

    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?

    opened by nerdoc 0
Releases(release/0.3.0)
  • release/0.3.0(Mar 27, 2022)

    • Stream class added to explicitly declare streams
      • Streams auto-detected in streams.py
      • TurboMixin has been removed. ModelStreams replace this functionality with linked model declared in Meta.model
    • Permissions can now be written by overriding the Stream.user_passes_test() method
    • Support for stream-less turbo-frame responses to POST requests
    Source code(tar.gz)
    Source code(zip)
  • 0.2.5(Dec 5, 2021)

    What's Changed

    • Turbo 7.1.0 library by @scuml in https://github.com/hotwire-django/turbo-django/pull/47

    Full Changelog: https://github.com/hotwire-django/turbo-django/compare/0.2.4...0.2.5

    Source code(tar.gz)
    Source code(zip)
  • 0.2.4(Sep 5, 2021)

  • 0.2.3(Aug 16, 2021)

    Major update to the turbo-django library. This includes breaking API changes but makes the library much more flexible to use.

    Broadcasting now centers around Turbo objects. Create a Turbo object with the broadcast name, render a template, and then call one of the actions to send the html to the subscribed clients.

    from turbo import Turbo
    
    # Send a message with the current timestamp to the channel `broadcast_name` and
    # update any element with the id 'broadcast_box`.
    Turbo('broadcast_name').render_from_string(
        f"{datetime.now()}: This is a broadcast."
    ).update(id="broadcast_box")
    
    # or for a model instance
    
    room = Room.objects.first()
    
    Turbo(room).render(
        "template.html", context={}
    ).append(id="broadcast_box")
    
    # If using TurboMixin
    
    room.turbo.render(
        "template.html", context={}
    ).append(selector="p.broadcast_class")
    
    

    broadcast_to and broadcast_self were removed as they placed severe limitations on what could be broadcast. They have been replaced with ModelBroadcasts. ModelBroadcasts live in the file broadcasts.py and look like this:

    @turbo.register(Message)
    class MessageBroadcast(turbo.ModelBroadcast):
    
        def on_save(self, message, created, *args, **kwargs):
            if created:
                message.room.turbo.render("chat/message.html", {"message": message}).append(id="messages")
            else:
                message.room.turbo.render("chat/message.html", {"message": message}).replace(id=f"message-{message.id}")
    
        def on_delete(self, message, *args, **kwargs):
            message.room.turbo.remove(id=f"message-{message.id}")
    

    This allows the user to explicitly send as many templates to as many channels as needed - and allows additional context to be sent to the template - all in a django-esque easy-to-read class.

    Other changes include:

    • Huge documentation update including a quickstart page and five-part tutorial.
    • {% turbo_stream_from %} has been renamed to {% turbo_subscribe %} and can now accept a list of channels to listen
    • BroadcastableMixin has been renamed TurboMixin and simply adds a .turbo attribute to the model instance.
    Source code(tar.gz)
    Source code(zip)
A simple package for interacting with the 9kw.eu anti-captcha service.

Welcome to captcha9kw’s documentation! captcha9kw is a smallish Python package for making use of the 9kw.eu services, including solving of interactive

2 Feb 26, 2022
Visualize Data From Stray Scanner https://keke.dev/blog/2021/03/10/Stray-Scanner.html

StrayVisualizer A set of scripts to work with data collected using Stray Scanner. Usage Installing Dependencies Install dependencies with pip -r requi

Kenneth Blomqvist 45 Dec 30, 2022
A compiler for ARM, X86, MSP430, xtensa and more implemented in pure Python

Introduction The PPCI (Pure Python Compiler Infrastructure) project is a compiler written entirely in the Python programming language. It contains fro

Windel Bouwman 277 Dec 26, 2022
Open source tools to allow working with ESP devices in the browser

ESP Web Tools Allow flashing ESPHome or other ESP-based firmwares via the browser. Will automatically detect the board type and select a supported fir

ESPHome 195 Dec 31, 2022
Prometheus exporter for chess.com player data

chess-exporter Prometheus exporter for chess.com player data implemented via chess.com's published data API and Prometheus Python Client Example use c

Mário Uhrík 7 Feb 28, 2022
An useful scripts for Misskey

misskey-scripts This place storing useful scripts which made by me. icon-repair Repair broken remote user's icon.

CyberRex 5 Sep 09, 2022
An Advanced Wordlist Library Written In Python For Acm114

RBAPG -RBAPG is the abbreviation of "Rule Based Attack Password Generator". -This module is a wordlist generator module. -You can generate randomly

Aziz Kaplan 11 Aug 28, 2022
A notebook explaining the principle of adversarial attacks and their defences

TL;DR: A notebook explaining the principle of adversarial attacks and their defences Abstract: Deep neural networks models have been wildly successful

1 Jan 22, 2022
A Trace Explorer for Reverse Engineers

Tenet - A Trace Explorer for Reverse Engineers Overview Tenet is an IDA Pro plugin for exploring execution traces. The goal of this plugin is to provi

1k Jan 02, 2023
Anonymous Dark Web Tool

Anonymous Dark Web Tool v1.0 Features Anonymous Mode Darkweb Search Engines Check Onion Url/s Scanning Host/IP Keep eyes on v2.0 soon. Requirement Deb

Mounib Kamhaz 11 Apr 10, 2022
The calculator on Python.

Calculator Contributors: Delitanast An official website. Information Hello! I am Damir. It`s my first Python project. I think you want see this. I imp

3 Mar 13, 2022
Python pyside2 kütüphanesi ile oluşturduğum drone için yer kontrol istasyonu yazılımı.

Ground Control Station (Yer Kontrol İstasyonu) Teknofest yarışmasında yerlilik kısmında Yer Kontrol İstasyonu yazılımı seçeneği bulunuyordu. Bu yüzden

Emirhan Bülbül 4 May 14, 2022
Your Google Recon is Now Automated

GRecon : GRecon (Greei-Conn) is a simple python tool that automates the process of Google Based Recon AKA Google Dorking The current Version 1.0 Run 7

adnane-tebbaa 189 Dec 21, 2022
Pokemon catch events project to demonstrate data pipeline on AWS

Pokemon Catches Data Pipeline This is a sample project to practice end-to-end data project; Terraform is used to deploy infrastructure; Kafka is the t

Vitor Carra 4 Sep 03, 2021
Linux Pressure Stall Information (PSI) Status App

Linux Pressure Stall Information (PSI) Status App psistat is a simple python3 program to display the PSIs and to capture/display exception events. psi

Joe D 3 Sep 18, 2022
Collection of system-wide scripts that I use on my Gentoo

linux-scripts Collection of scripts that I use on my Gentoo machine. I tend to put all scripts in /scripts directory. It is not likely that you would

Xoores 1 Jan 09, 2022
Cool Bioinformatics Scripts

Cool Bioinformatics Scripts qqplot You can use this script in two ways read tons of millions of P values from stdin # python zcat pval.txt.gz | qqplo

8 Oct 30, 2022
Python scripts to interact with Upper Deck ePack online trading card platform

This script should connect to the Upper Deck ePack API using your browser cookies and download a list of your current collection and save it as a CSV.

Adrian Kent 1 Nov 22, 2021
1 May 12, 2022
Simple AoC helper program you can use to develop your own solutions in python.

AoC-Compabion Simple AoC helper program you can use to develop your own solutions in python. Simply install it in your python environment using pip fr

Alexander Vollmer 1 Dec 20, 2021