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)
Would upload anything I do with/related to brainfuck

My Brainfu*k Repo Basically wanted to create something with Brainfu*k but realized that with the smol brain I have, I need to see the cell values real

Rafeed 1 Mar 22, 2022
Ghost source since the developer of the project quit due to reasons

👻 Ghost Selfbot The official code for Ghost which was recently discontinued and released to the public. Feel free to use any of the code found in thi

xannyy 2 Mar 24, 2022
A project to find out all the words in a crossword.

A project to find out all the words in a crossword.

Kalpesh Dhoundiyal 1 Feb 06, 2022
This is some simple code to scrape vistbook's system to get an overview of the different cabins availability.

DNT_cabin_availability_system This is some simple code to scrape visbook's system to get an overview of the different cabins availability. The system

Andreas Lorentzen 1 Sep 25, 2022
Watcher for systemdrun user scopes

Systemctl Memory Watcher Animated watcher for systemdrun user scopes. Usage Launch some process in your GNU-Linux or compatible OS with systemd-run co

Antonio Vanegas 2 Jan 20, 2022
Un script en python qui permet d'automatique bumpée (disboard.org) tout les 2h

auto-bumper Un script en python qui permet d'automatique bumpée (disboard.org) tout les 2h Pour la première utilisation, 1.Lancer Install.bat 2.(faire

!! 1 Jan 09, 2022
List Less Than Ten with python

List Less Than Ten with python

PyLaboratory 0 Feb 07, 2022
A type based dependency injection framework for Python 3.9+

Alluka A type based dependency injection framework for Python 3.9+. Installation You can install Alluka from PyPI using the following command in any P

Lucina 16 Dec 15, 2022
Blender Add-on That Provides Quick Access to Render Controls

Blender Render Buttons Blender Add-on That Provides Quick Access to Render Controls A Blender 3.0 compatablity update of Blender2.8x-RenderButton v0.0

Don Schnitzius 3 Oct 18, 2022
Kellogg bad | Union good | Support strike funds

KelloggBot Credit to SeanDaBlack for the basis of the script. req.py is selenium python bot. sc.js is a the base of the ios shortcut [COMING SOON] Set

407 Nov 17, 2022
Your one and only Discord Bot that helps you concentrate!

Your one and only Discord Bot thats helps you concentrate! Consider leaving a ⭐ if you found the project helpful. concy-bot A bot which constructively

IEEE VIT Student Chapter 22 Sep 27, 2022
Scripts used in the RayStation medical radiation dosimetry treatment planning system

Med Phys Scripts These are scripts that I, the medical physics assistant at Cookeville Regional Medical Center, wrote for use in our radiation therapy

Kaley White 2 Oct 19, 2022
An open source server for Super Mario Bros. 35

SMB35 A custom server for Super Mario Bros. 35 This server is highly experimental. Do not expect it to work without flaws.

Yannik Marchand 162 Dec 07, 2022
A repo to record how I prepare my Interview, and really hope it can help you as well. Really appreciate Kieran's help in the pattern's part.

Project Overview The purpose of this repo is to help others to find solutions and explaintion I will commit a solution and explanation to every proble

Vincent Zhenhao ZHAO 1 Nov 29, 2021
Multi View Stereo on Internet Images

Evaluating MVS in a CPC Scenario This repository contains the set of artficats used for the ENGN8601/8602 research project. The thesis emphasizes on t

Namas Bhandari 1 Nov 10, 2021
A topology optimization framework written in Taichi programming language, which is embedded in Python.

Taichi TopOpt (Under Active Development) Intro A topology optimization framework written in Taichi programming language, which is embedded in Python.

Li Zhehao 41 Nov 17, 2022
Easy way to build a SaaS application using Python and Dash

EasySaaS This project will be attempt to make a great starting point for your next big business as easy and efficent as possible. This project will cr

xianhu 3 Nov 17, 2022
Tenda D151 & D301 - Unauthenticated configuration download

Exploit Title: Tenda D151 & D301 - Unauthenticated configuration download (login included)

Ayoub 3 Jul 14, 2022
This library attempts to abstract the handling of Sigma rules in Python

This library attempts to abstract the handling of Sigma rules in Python. The rules are parsed using a schema defined with pydantic, and can be easily loaded from YAML files into a structured Python o

Caleb Stewart 44 Oct 29, 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