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)
Semester long, web application project for CSCI 4370/6370 (Database Management)

Database_Project Prototype ideas for website: Computer Science library (Sells books, products, etc.) Code editor Graph visualizer / creator (can save

Jordan Harman 4 Feb 17, 2022
Supercharge your NFTs with new behaviours and superpowers!

WrapX Supercharge your NFTs with new behaviours and superpowers! WrapX is a collection of Wrappers (currently one - WrapXSet) to decorate your NTFs ad

Emiliano Bonassi 9 Jun 13, 2022
Anki Addon idea by gbrl.sc to see previous ratings of a card in the reviewer

Card History At A Glance Stop having to press card browser and ctrl+i for every card and then WINCING to see it's history of reviews FEATURES Visualiz

Jerry Zhou 11 Dec 19, 2022
Adansons Base is a data management tool that organizes metadata of unstructured data and creates and organizes datasets.

Adansons Base is a data management tool that organizes metadata of unstructured data and creates and organizes datasets. It makes dataset creation more effective and helps find essential insights fro

Adansons Inc 27 Oct 22, 2022
Generate Openbox Menus from a easy to write configuration file.

openbox-menu-generator Generate Openbox Menus from a easy to write configuration file. Example Configuration: ('#' indicate comments but not implement

3 Jul 14, 2022
Github dorking tool

gh-dork Supply a list of dorks and, optionally, one of the following: a user (-u) a file with a list of users (-uf) an organization (-org) a file with

Molly White 119 Dec 21, 2022
⏰ Shutdown Timer is an application that you can shutdown, restart, logoff, and hibernate your computer with a timer.

Shutdown Timer is a an application that you can shutdown, restart, logoff, and hibernate your computer with a timer. After choosing an action from the

Mehmet Güdük 5 Jun 27, 2022
Small pip update helpers.

pipdate pipdate is a collection of small pip update helpers. The command pipdate # or python3.9 -m pipdate updates all your pip-installed packages. (O

Nico Schlömer 69 Dec 18, 2022
A sage package for working with circular genomes represented by signed or unsigned permutations

Circular genome tools (cgt) A sage package for working with circular genomes represented by signed or unsigned permutations. It includes tools for con

Joshua Stevenson 1 Mar 10, 2022
Kivy program for identification & rotation sensing of objects on multi-touch tables.

ObjectViz ObjectViz is a multitouch object detection solution, enabling you to create physical markers out of any reliable multitouch solution. It's e

TangibleDisplay 8 Apr 04, 2022
Malicious Document IoC Extractor is a collection of scripts that helps extracting IoCs from various maldoc families.

MDIExtractor Malicious Document IoC Extractor (MDIExtractor) is a collection of scripts that helps extracting IoCs from various maldoc families. Prere

Malwrologist 14 Nov 25, 2022
Grail(TM) is a web browser written in Python

Grail is distributed in source form. It requires that you have a Python interpreter and a Tcl/Tk installation, with the Python interpreter configured for Tcl/Tk support.

22 Oct 18, 2022
Standard mutable string (character array) implementation for Python.

chararray A standard mutable character array implementation for Python.

Tushar Sadhwani 3 Dec 18, 2021
Powerful virtual assistant in python

Virtual assistant in python Powerful virtual assistant in python Set up Step 1: download repo and unzip Step 2: pip install requirements.txt (if py au

Arkal 3 Jan 23, 2022
Various hdas (Houdini Digital Assets)

aaTools My various assets for Houdini "ms_asset_loader" - Custom importer assets from Quixel Bridge "asset_placer" - Tool for placment sop geometry on

9 Dec 19, 2022
This is a calculator of strike price distance for options.

Calculator-of-strike-price-distance-for-options This is a calculator of strike price distance for options. Options are a type of derivative. One strat

André Luís Lopes da Silva 4 Dec 30, 2022
It is convenient to quickly import Python packages from the network.

It is convenient to quickly import Python packages from the network.

zmaplex 1 Jan 18, 2022
Bad Apple printed out on the console with Python!

bad-apple Bad Apple printed out on the console with Python! Preface A word of disclaimer, while the final code is somewhat original, this project is a

CalvinLoke 186 Dec 01, 2022
MobaXterm-GenKey

MobaXterm-GenKey 你懂的!! 本地启动 需要安装Python3!!!

malaohu 328 Dec 29, 2022
Larvamatch - Find your larva or punk match.

LarvaMatch Find your larva or punk match. UI TBD API (not started) The API will allow you to specify a punk by token id to find a larva match, and vic

1 Jan 02, 2022