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)
Cirq is a Python library for writing, manipulating, and optimizing quantum circuits and running them against quantum computers and simulators

Cirq is a Python library for writing, manipulating, and optimizing quantum circuits and running them against quantum computers and simulators. Install

quantumlib 3.6k Jan 07, 2023
Very Simple Zoom Spam Pinger!

Very Simple Zoom Spam Pinger!

Syntax. 2 Mar 05, 2022
a simple functional programming language compiler written in python

Functional Programming Language A compiler for my small functional language. Written in python with SLY lexer/parser generator library. Requirements p

Ashkan Laei 3 Nov 05, 2021
eyes is a Public Opinion Mining System focusing on taiwanese forums such as PTT, Dcard.

eyes is a Public Opinion Mining System focusing on taiwanese forums such as PTT, Dcard. Features 🔥 Article monitor: helps you capture the trend at a

Sean 116 Dec 29, 2022
Course materials for a 3-day seminar "Machine Learning and NLP: Advances and Applications" at New College of Florida

Machine Learning and NLP: Advances and Applications This repository hosts the course materials used for a 3-day seminar "Machine Learning and NLP: Adv

Yoshi Suhara 11 Jun 22, 2022
The Doodle Master seeks to turn your UI mockups into real code.

Doodle Master The Doodle Master seeks to turn your UI mockups into real code. Currently this repository just serves to demonstrate a Proof Of Concept

Karanbir Chahal 2.4k Dec 09, 2022
A chain of stores wants a 3-month demand forecast for its 10 different stores and 50 different products.

Demand Forecasting Objective A chain store wants a machine learning project for a 3-month demand forecast for 10 different stores and 50 different pro

2 Jan 06, 2022
urlwatch is intended to help you watch changes in webpages and get notified of any changes.

urlwatch is intended to help you watch changes in webpages and get notified (via e-mail, in your terminal or through various third party services) of any changes.

Thomas Perl 2.5k Jan 08, 2023
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
Code and yara rules to detect and analyze Cobalt Strike

Cobalt Strike Resources This repository contains: analyze.py: a script to analyze a Cobalt Strike beacon (python analyze.py BEACON) extract.py; extrac

Tek 224 Jan 04, 2023
This bot uploads telegram files to MixDrop.co,File.io.

What is about this bot ? This bot uploads telegram files to MixDrop.co, File.io. Usage: Send any file, and the bot will upload it to MixDrop.co, File.

Abhijith NT 3 Feb 26, 2022
use Notepad++ for real-time sync after python appending new log text

FTP远程log同步工具 使用Notepad++配合来获取实时更新的log文档效果 适用于FTP协议的log远程同步工具,配合MT管理器开启FTP服务器使用,通过Notepad++监听文本变化,更便捷的使用电脑查看方法注入打印后的信息 功能 过滤器 对每行要打印的文本使用回调函数筛选,支持链式调用

Liuhaixv 1 Oct 17, 2021
A student information management system in Python

Student-information-management-system 本项目是一个学生信息管理系统,这个项目是用Python语言实现的,也实现了图形化界面的显示,同时也实现了管理员端,学生端两个登陆入口,同时底层使用的是Redis做的数据持久化。 This project is a stude

liuyunfei 7 Nov 15, 2022
Rename and categorize your DMOJ solutions

DMOJ Downloader What is this for? DMOJ lets you download the code for all your solutions, however the files are just named as numbers

Evan Wild 1 Dec 04, 2022
Semantic Data Management - Property Graphs 📈

SDM - Lab 1 @ UPC 👨🏻‍💻 Table of contents Introduction Property Graph Dataset 1. Introduction This repo is all about what we have done in SDM lab 1

Mohammad Zain Abbas 1 Mar 20, 2022
Send notifications created in Frappe or ERPNext as push notication via Firebase Cloud Message(FCM)

FCM Notification for ERPNext Send notifications created in Frappe or ERPNext as push notication via Firebase Cloud Message(FCM) Steps to use the app:

Tridz 9 Nov 14, 2022
Visualization of COVID-19 Omicron wave data in Seoul, Osaka, Tokyo, Hong Kong and Shanghai. 首尔、大阪、东京、香港、上海由新冠病毒 Omicron 变异株引起的本轮疫情数据可视化分析。

COVID-19 in East Asian Megacities This repository holds original Python code for processing and visualization COVID-19 data in East Asian megacities a

STONE 10 May 18, 2022
A Python wrapper for Matrix Synapse admin API

Synapse-admin-api-python A Python wrapper for Matrix Synapse admin API. Versioning This library now supports up to Synapse 1.45.0, any Admin API intro

Knugi 9 Sep 28, 2022
management tool for systemd-nspawn containers

nspctl nspctl, management tool for systemd-nspawn containers. Why nspctl? There are different tools for systemd-nspawn containers. You can use native

Emre Eryilmaz 5 Nov 27, 2022
Python script to autodetect a base set of swiftlint rules.

swiftlint-autodetect Python script to autodetect a base set of swiftlint rules. Installation brew install pipx

Jonathan Wight 24 Sep 20, 2022