Phoenix LiveView but for Django

Overview

Reactor, a LiveView library for Django

Reactor enables you to do something similar to Phoenix framework LiveView using Django Channels.

TODO MVC demo app

What's in the box?

This is no replacement for VueJS or ReactJS, or any JavaScript but it will allow you use all the potential of Django to create interactive front-ends. This method has its drawbacks because if connection is lost to the server the components in the front-end go busted until connection is re-established. But also has some advantages, as everything is server side rendered the interface comes already with meaningful information in the first request response, you can use all the power of Django template without limitations, if connection is lost or a component crashes, the front-end will have enough information to rebuild their state in the last good known state.

Installation and setup

Reactor requires Python >=3.6.

Install reactor:

pip install django-reactor

Reactor makes use of django-channels, by default this one uses an InMemory channel layer which is not capable of a real broadcasting, so you might wanna use the Redis one, take a look here: Channel Layers

Add reactor and channels to your INSTALLED_APPS before the Django applications so channels can override the runserver command.

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

...

ASGI_APPLICATION = 'project_name.asgi.application'

and modify your project_name/asgi.py file like:

import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project_name.settings')

import django
django.setup()

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from reactor.urls import websocket_urlpatterns

application = ProtocolTypeRouter({
    'http': get_asgi_application(),
    'websocket': AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
})

Note: Reactor since version 2, autoloads any live.py file in your applications with the hope to find there Reactor Components so they get registered and can be instantiated.

In the templates where you want to use reactive components you have to load the reactor static files. So do something like this so the right JavaScript gets loaded:

{% load reactor %}
<!doctype html>
<html>
  <head>
     ....
     {% reactor_header %}
  </head>
  ...
</html>

Don't worry if you put this as early as possible, the scripts are loaded using <script defer> so they will be downloaded in parallel with the html, and then all is loaded they are executed.

Settings:

  • REACTOR_AUTO_BROADCAST (default: False), when enabled will activate listeners for every time a model is created, modified or deleted, and will broadcast a message related to that modification that you can subscribe to and use to refresh your components in real-time, you can fine tune what kind of notification you want to get by turning this in a dictionary, for example:
AUTO_BROADCAST = {
    # model_a
    # model_a.del
    # model_a.new
    'MODEL': True,

    # model_a.1234
    'MODEL_PK': True,

    # model_b.1234.model_a_set
    # model_b.1234.model_a_set.new
    # model_b.1234.model_a_set.del
    'RELATED': True,

    # model_b.1234.model_a_set
    # model_a.1234.model_b_set
    'M2M': True,
}
  • REACTOR_USE_HTML_DIFF (default: True), when enabled uses difflib to create diffs to patch the front-end, reducing bandwidth.
  • REACTOR_USE_HMIN (default: False), when enabled and django-hmin is installed will use it to minified the HTML of the components and save bandwidth.

Back-end APIs

Template tags and filters of reactor library

  • {% reactor_headers %}: that includes the necessary JavaScript to make this library work. ~5Kb of minified JS, compressed with gz or brotli.
  • {% component 'x-component-name' param1=1 param2=2 %}: Renders a component by its name and passing whatever parameters you put there to the Component.mount method.
  • tojson: Takes something and renders it in JSON, the ReactorJSONEncoder extends the DjangoJSONEncoder it serializes a Model instance to its id and a QuerySet as a list of ids.
  • tojson_safe: Same as tojson but does not "HTML escapes" the output.
  • then: Use as a shorthand for if, {% if expression %}print-this{% endif %} is equivalent to {{ expresssion|then:'print-this' }}.
  • ifnot: Use a shorthand for if not, {% if not expression %}print-this{% endif %} is equivalent to {{ expresssion|ifnot:'print-this' }}, and can be concatenated with then, like in: {{ expression|then:'positive'|ifnot:'negative' }}`
  • eq: Compares its arguments and returns "yes" or empty string, {{ this_thing|eq:other_thing|then:'print-this' }}.
  • cond: Allows simple conditional presence of a string: {% cond {'hidden': is_hidden } %}.
  • class: Use it to handle conditional classes: <div {% class {'nav_bar': True, 'hidden': is_hidden} %}></div>.

reactor.component module

  • Component: This is the base component you should extend.
  • AuthComponent: Extends Component and ensures the user is logged in.
  • broadcast(*names): Broadcasts the given names too all the system.
  • on_commit(function)(*args, **kwargs): Calls function with the given arguments after database commit.

Component API

  • __init__: Is responsable for the component initialization, pass what ever you need to bootstrap the component state.
  • template_name: Set the name of the template of the component.
  • extends: Tag name HTML element the component extends.
  • _subscribe(*names): Subscribes the current component to the given signal names, when one of those signals is broadcasted the component is refreshed, meaning that mount is called passing the result serialize and the component is re-rendered.
  • visit(url, action='advance', **kwargs ): Resolves the url using **kwargs, and depending on action the navigation will be advance (pushState) or replace (repalceState).
  • destroy(): Removes the component from the interface.
  • _send(_name, id=None, **kwargs): Sends a message with the name _name to the component with id, if id is None the message is sent to the current component.
  • _send_parent(_name, kwargs): Sends a message with the name _name to the parent component.

Front-end APIs

  • reactor.visit(url, {action='advance'}): if action is advance, calls window.history.replaceState, else tries to talk to Turbo or falls back to window.history.pushState or just window.location.assign.
  • reactor.send(element, event_name, args): send the event event_name with the args parameters to the HTML element. It what is used to forward user event to the back-end.

Special HTMLElement attributes

  • :keep: Prevent the value of an input from being changed across renders.
  • :override: When an input is being updated and the user has the focus there reactor by default will not update the input field value (has if it had :keep), use :override to do otherwise.
  • :once: Reactor will render this element and children once, and never update it again.
  • :focus: Sets the focus on this element after an HTML update.

Event binding in the front-end

Look at this:

  <button @click.prevent="submit">Submit</button?>

The format is @<event>[.modifier][.modifier]="event_name[ {arg1: 1, arg2: '2'}]":

  • event: is the name of the HTMLElement event: click, blur, change, keypress, keyup, keydown...
  • modifier: can be concatenated after the event name and represent actions or conditions to be met before the event execution. This is very similar as how VueJS does event binding:
    • prevent: calls event.preventDefault();
    • stop: calls (event.stopPropagation();),
    • enter, ctrl, alt, space, expects any of those keys to be press.
    • inlinejs: allows you to write your custom JavaScript in the event handler.
    • debounce: debounces the event, it needs a name and a delay in milliseconds. Example: @keypress.100.search.debounce='message'.
  • event_name: is the name of the message to be send to this component
  • The arguments can be completely omitted, or specified as a dictionary.

When the arguments are omitted, reactor serializes the form where the current element is or the current component if no form is found, and sends that as the arguments. The arguments will be always sent with the id of the current component as a parameter.

JS Hooks

These are custom events triggered by reactor in different instants of the life cycle of the component.

  • onreactor-init: Triggered on any HTML element when the component is initialized.
  • onreactor-added: Triggered on any HTML element that is added to the DOM of the component.
  • onreactor-updated: Triggered on any HTML element that is updated, after the update happens.
  • onreactor-leave: Triggered on the root element when the element had been removed from the DOM.

Event handlers in the back-end

Given:

<button @click="inc {amount: 2}">Increment</button?>

You will need an event handler in that component in the back-end:

def inc(self, amount: int):
    pass

Simple example of a counter

In your app create a template x-counter.html:

{% load reactor %}
<div {% tag_header %}>
  {{ amount }}
  <button @click="inc">+</button>
  <button @click="dec">-</button>
  <button @click="set_to {amount: 0}">reset</button>
</div>

Anatomy of a template: each component should be a custom web component that inherits from HTMLElement. They should have an id so the backend knows which instance is this one and a state attribute with the necessary information to recreate the full state of the component on first render and in case of re-connection to the back-end.

Render things as usually, so you can use full Django template language, trans, if, for and so on. Just keep in mind that the instance of the component is referred as this.

Forwarding events to the back-end: Notice that for event binding in-line JavaScript is used on the event handler of the HTML elements. How does this work? When the increment button receives a click event send(this, 'inc') is called, send is a reactor function that will look for the parent custom component and will dispatch to it the inc message, or the set_to message and its parameters {amount: 0}. The custom element then will send this message to the back-end, where the state of the component will change and then will be re-rendered back to the front-end. In the front-end morphdom (just like in Phoenix LiveView) is used to apply the new HTML.

Now let's write the behavior part of the component in live.py:

from reactor import Component


class XCounter(Component):
    template_name = 'x-counter.html'

    def __init__(self, amount: int = 0, **kwargs):
        super().__init__(**kwargs)
        self.amount = amount

    def inc(self):
        self.amount += 1

    def dec(self):
        self.amount -= 1

    def set_to(self, amount: int):
        self.amount = amount

Let's now render this counter, expose a normal view that renders HTML, like:

def index(request):
    return render(request, 'index.html')

And the index template being:

{% load reactor %}
<!doctype html>
<html>
  <head>
     ....
     {% reactor_header %}
  </head>
  <body>
    {% component 'x-counter' %}

    <!-- or passing an initial state -->
    {% component 'x-counter' amount=100 %}

  </body>
</html>

Don't forget to update your urls.py to call the index view.

More complex components

I made a TODO list app using models that signals from the model to the respective channels to update the interface when something gets created, modified or deleted.

This example contains nested components and some more complex interactions than a simple counter, the app is in the /tests/ directory.

Development & Contributing

Clone the repo and create a virtualenv or any other contained environment, get inside the repo directory, build the development environment and the run tests.

git clone [email protected]:edelvalle/reactor.git
cd reactor
make install
make test

If you want to run the included Django project used for testing do:

make
cd tests
python manage.py runserver

Enjoy!

Comments
  • daphne/channels update

    daphne/channels update

    Hi, just trying to setup reactor. If I make a plain python project, and just do a pip install django-reactor as recommended, I get:

    channels==2.4.0
    daphne==2.5.0
    Django==3.2.4
    django-reactor==2.2.1b0
    etc.
    

    But, in your poetry.lock you have channels version = "3.0.3", and daphne = ">=3.0,<4".

    How comes this? (BTW, I didn't get the websockets part working yet... still struggling)

    opened by nerdoc 9
  • Error when connecting to the WebSocket

    Error when connecting to the WebSocket

    When loading up the sample increment/decrement code, the WebSocket seemed to never connect. This was the error I was seeing in error logs:

    File "/Users/.../Library/Caches/pypoetry/virtualenvs/django-reactor-py3.7/lib/python3.7/site-packages/reactor/channels.py", line 44, in connect
        self.scope['channel_name'] = self.channel_name
      'ReactorConsumer' object has no attribute 'channel_name'
    
    opened by adamghill 8
  • Cannot get subscriptions to work, _channel_name is None

    Cannot get subscriptions to work, _channel_name is None

    When I call self._subscribe on my components, nothing happens. I have stepped through the code with a debugger and it seems as if the property channel_name is always empty, so the check if _channel_name in def send_to_channel is always false for me and thus nothing happens when it's called.

    Is this a bug or am I missing something in my configuration? I was unable to figure out how the property was supposed to get its value.

    This is with the Redis channel layer, on version 2.2.1.b with Django 3.1.7 and Python 3.7

    opened by TomasLoow 4
  • Allow sending arbitrary notifications to a Component.

    Allow sending arbitrary notifications to a Component.

    Adds the reactor.component.broadcast(channel, **kwargs) to send a notification to any Component subscribed to channel. he message is delivered in Component.notification(channel, **kwargs).

    opened by edelvalle 3
  • Allow components to encode part of their state in the URL

    Allow components to encode part of their state in the URL

    By defining this:

    class Search(Component):
        query: str
    
        _url_params = {"query": "search_query"}
    

    When the object state changes it will replace the URL updating the GET parameters to include search_query=whatever so the state of the view can be restored when the URL is shared or the user goes back to it.

    opened by edelvalle 2
  • ModuleNotFoundError: No module named 'reactor.urls'

    ModuleNotFoundError: No module named 'reactor.urls'

    I followed the README and encountered this error:

    File "/home/me/project/app/asgi.py", line 34, in from reactor.urls import websocket_urlpatterns ModuleNotFoundError: No module named 'reactor.urls'

    from channels.auth import AuthMiddlewareStack
    from channels.routing import ProtocolTypeRouter, URLRouter
    from reactor.urls import websocket_urlpatterns
    
    application = ProtocolTypeRouter({
        'http': get_asgi_application(),
        'websocket': AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
    })
    

    There is no urls file in the package. Where am I supposed to get websocket_urlpatterns from?

    opened by richarddewit 2
  • Always use id when consructing Component objects

    Always use id when consructing Component objects

    Hi @edelvalle! Great module. I'm just experimenting, but I found if a component is nested inside another component, I found that id was not being set properly as it was not included in Component._build if _parent_id is set.

    Disclaimer: I've only very briefly looked at the internals and unfortunately my Python segfaults when I try to run tests.py, so apologies if this is not a useful PR.

    opened by kbni 2
  • Bug? '__signature__' attribute of 'BlockEditor' is class-only

    Bug? '__signature__' attribute of 'BlockEditor' is class-only

    When using the simplest possible reactor component, I always get this error... ATM, current reactor is not installable via pip. It's only possible using the git version, so you have to use this one, not the Pypi package.

    I've put together a small demo app, that basically creates a small component and includes it in a simple template.

    It always raises this error:

    '__signature__' attribute of 'BlockEditor' is class-only`.
    Exception Location: | pydantic/utils.py, line 612, in pydantic.utils.ClassAttribute.__get__
    Python Version: | 3.10.2
    

    during rendering of {% component "TestComponent" %}.

    I can't figure out what this means.

    opened by nerdoc 1
  • Bump pydantic from 1.7.3 to 1.7.4

    Bump pydantic from 1.7.3 to 1.7.4

    Bumps pydantic from 1.7.3 to 1.7.4.

    Release notes

    Sourced from pydantic's releases.

    v1.7.4 (2021-05-11)

    Security fix: Fix date and datetime parsing so passing either 'infinity' or float('inf') (or their negative values) does not cause an infinite loop, see security advisory CVE-2021-29510.

    Changelog

    Sourced from pydantic's changelog.

    v1.7.4 (2021-05-11)

    • Security fix: Fix date and datetime parsing so passing either 'infinity' or float('inf') (or their negative values) does not cause an infinite loop, See security advisory CVE-2021-29510
    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 1
  • Bump urllib3 from 1.26.3 to 1.26.5

    Bump urllib3 from 1.26.3 to 1.26.5

    Bumps urllib3 from 1.26.3 to 1.26.5.

    Release notes

    Sourced from urllib3's releases.

    1.26.5

    :warning: IMPORTANT: urllib3 v2.0 will drop support for Python 2: Read more in the v2.0 Roadmap

    • Fixed deprecation warnings emitted in Python 3.10.
    • Updated vendored six library to 1.16.0.
    • Improved performance of URL parser when splitting the authority component.

    If you or your organization rely on urllib3 consider supporting us via GitHub Sponsors

    1.26.4

    :warning: IMPORTANT: urllib3 v2.0 will drop support for Python 2: Read more in the v2.0 Roadmap

    • Changed behavior of the default SSLContext when connecting to HTTPS proxy during HTTPS requests. The default SSLContext now sets check_hostname=True.

    If you or your organization rely on urllib3 consider supporting us via GitHub Sponsors

    Changelog

    Sourced from urllib3's changelog.

    1.26.5 (2021-05-26)

    • Fixed deprecation warnings emitted in Python 3.10.
    • Updated vendored six library to 1.16.0.
    • Improved performance of URL parser when splitting the authority component.

    1.26.4 (2021-03-15)

    • Changed behavior of the default SSLContext when connecting to HTTPS proxy during HTTPS requests. The default SSLContext now sets check_hostname=True.
    Commits
    • d161647 Release 1.26.5
    • 2d4a3fe Improve performance of sub-authority splitting in URL
    • 2698537 Update vendored six to 1.16.0
    • 07bed79 Fix deprecation warnings for Python 3.10 ssl module
    • d725a9b Add Python 3.10 to GitHub Actions
    • 339ad34 Use pytest==6.2.4 on Python 3.10+
    • f271c9c Apply latest Black formatting
    • 1884878 [1.26] Properly proxy EOF on the SSLTransport test suite
    • a891304 Release 1.26.4
    • 8d65ea1 Merge pull request from GHSA-5phf-pp7p-vc2r
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 1
  • Bump sqlparse from 0.4.1 to 0.4.2

    Bump sqlparse from 0.4.1 to 0.4.2

    Bumps sqlparse from 0.4.1 to 0.4.2.

    Changelog

    Sourced from sqlparse's changelog.

    Release 0.4.2 (Sep 10, 2021)

    Notable Changes

    Enhancements

    • Add ELSIF as keyword (issue584).
    • Add CONFLICT and ON_ERROR_STOP keywords (pr595, by j-martin).

    Bug Fixes

    • Fix parsing of backticks (issue588).
    • Fix parsing of scientific number (issue399).
    Commits
    • b1f76f6 Update changelog.
    • 3eec44e Update Changelog and bump version.
    • 8238a9e Optimize regular expression for identifying line breaks in comments.
    • e660467 Fix parsing of scientific numbers (fixes #399).
    • 23d2993 Update authors and changelog.
    • acc2810 keyword, add ON_ERROR_STOP
    • 282bcf1 keyword, add CONFLICT to postgres keywords
    • 63885dd Add ELSIF as keyword (fixes #584).
    • e575ae2 Fix parsing of backticks (fixes #588).
    • fe39072 Switch back to development mode.
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 1
  • Missing 2.2.1b0 tag on GitHub

    Missing 2.2.1b0 tag on GitHub

    Hi guys !

    I am trying to learn how django-reactor is working.

    When I installed it with pipenv, version 2.2.1b0 was installed but this tag is not available on GitHub, only on pypi.

    I would like to run the test project in tests repository of the last release.

    What is the best version to learn ? Master branch, 2.2.1b0 ou 2.2.0b ?

    opened by FlavienRx 0
  • AttributeError: '__signature__' attribute of 'XCounter' is class-only

    AttributeError: '__signature__' attribute of 'XCounter' is class-only

    Hello,

    I try to apply the reactor readme. I written this example, I try to use a component. Unfortunately, I get the error AttributeError: '__signature__' attribute of 'XCounter' is class-only.

    Do can you inform myself of what I forget?

    This is my full traceback logs:

      File "/usr/local/lib/python3.10/site-packages/asgiref/sync.py", line 472, in thread_handler
        raise exc_info[1]
      File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 42, in inner
        response = await get_response(request)
      File "/usr/local/lib/python3.10/site-packages/django/core/handlers/base.py", line 253, in _get_response_async
        response = await wrapped_callback(
      File "/usr/local/lib/python3.10/site-packages/asgiref/sync.py", line 435, in __call__
        ret = await asyncio.wait_for(future, timeout=None)
      File "/usr/local/lib/python3.10/asyncio/tasks.py", line 408, in wait_for
        return await fut
      File "/usr/local/lib/python3.10/concurrent/futures/thread.py", line 58, in run
        result = self.fn(*self.args, **self.kwargs)
      File "/usr/local/lib/python3.10/site-packages/asgiref/sync.py", line 476, in thread_handler
        return func(*args, **kwargs)
      File "/app/navi2/views.py", line 21, in counter
        return render(request, 'counter.html', context={"title": "index"})
      File "/usr/local/lib/python3.10/site-packages/django/shortcuts.py", line 24, in render
        content = loader.render_to_string(template_name, context, request, using=using)
      File "/usr/local/lib/python3.10/site-packages/django/template/loader.py", line 62, in render_to_string
        return template.render(context, request)
      File "/usr/local/lib/python3.10/site-packages/django/template/backends/django.py", line 62, in render
        return self.template.render(context)
      File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 175, in render
        return self._render(context)
      File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 167, in _render
        return self.nodelist.render(context)
      File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 1005, in render
        return SafeString("".join([node.render_annotated(context) for node in self]))
      File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 1005, in <listcomp>
        return SafeString("".join([node.render_annotated(context) for node in self]))
      File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 966, in render_annotated
        return self.render(context)
      File "/usr/local/lib/python3.10/site-packages/django/template/library.py", line 237, in render
        output = self.func(*resolved_args, **resolved_kwargs)
      File "/app/src/djangoreactor/reactor/templatetags/reactor.py", line 54, in component
        return component.render(repo) or ""
      File "/app/src/djangoreactor/reactor/component.py", line 338, in render
        return self.reactor.render(self, repo)
      File "/app/src/djangoreactor/reactor/component.py", line 140, in render
        context = self._get_context(component, repo)
      File "/app/src/djangoreactor/reactor/component.py", line 171, in _get_context
        {
      File "/app/src/djangoreactor/reactor/component.py", line 172, in <dictcomp>
        attr: getattr(component, attr)
      File "pydantic/utils.py", line 614, in pydantic.utils.ClassAttribute.__get__
    AttributeError: '__signature__' attribute of 'XCounter' is class-only
    

    Thank's by advance

    opened by adjivas 0
  • error at websocket WebSocket connection to 'wss://example.com/__reactor__' failed: Error during WebSocket handshake: Unexpected response code: 301

    error at websocket WebSocket connection to 'wss://example.com/__reactor__' failed: Error during WebSocket handshake: Unexpected response code: 301

    I followed the readme instructions very carefully, but this error is always raising the same way(in browser's console): WebSocket connection to 'wss://<example.com>/__reactor__' failed: Error during WebSocket handshake: Unexpected response code: 301 <example.com> is the domain name

    opened by hazho 0
  • Rationals for the commit

    Rationals for the commit "Huge blob commit to drive reactor forward"

    Hi @edelvalle, glad to see that you came back to this project !

    Do you have any rationals about your complete rewrite of reactor ?

    • transpilation in python,
    • pydantic serialization,
    • more...

    I remember that you wanted to avoid external dependencies and pydantic is a huge one. Is this for speed reason ? Cheers,

    opened by jbjuin 14
  • understanding reactor

    understanding reactor

    Let's say I use reactor to build a big table of 1,000 rows. I want to allow the user to edit 1 row. How would reactor do this? I understand morphdom can replace html content as below, but how would I change only 1 row? I see you are using the uuid4 library so I am guessing each element has an id? Does it mean every time you send an event from client to server you also send the element id?

    Another question, is it true all application state in reactor is in the actual html? I am not used to this thinking, it makes a lot of sense to me, but I am used to virtual doms so I wonder if this method has any drawbacks? I am guessing in my 1,000 rows example above the entire state would be in the <table> component, right? Does it mean that if there is a connection issue the server relies on the client html to get the latest available state and re-mount the component?

    I must say I would love to use reactor but I am a heavy flask user so I am also considering a port to flask of reactor.

    opened by acivitillo 2
Releases(1.2.0b0)
  • 1.2.0b0(Jun 24, 2019)

    Breaking changes in how to test components, for better

    Using async with reactor() as comm, you get an object that you can use to register components, send commands... doc is a PyQuery object containing the HTML rendering of the object, so you can perform checkings of the rendering of the object.

    from pytest import mark
    from channels.db import database_sync_to_async as db
    from reactor.tests import reactor
    
    @mark.django_db(transaction=True)
    async def test_live_components():
            x_list = comm.add_component(
                'x-todo-list',
                {'id': 'someid', 'showing': 'all'}
            )
            x_todo_counter = comm.add_component(
                'x-todo-counter',
                {'id': 'someid-counter'}
            )
            doc = await comm.send_join(x_list)
            todo_list = doc('#someid')
            assert json.loads(todo_list.attr['state']) == comm[x_list].state
    
            # Add new item
            doc = await comm.send(x_list, 'add', new_item='First task')
    
            # There was an item crated and rendered
            assert await db(Item.objects.count)() == 1
            assert len(doc('x-todo-item')) == 1
            todo_item_id = doc('x-todo-item')[0].get('id')
            todo_item_label = doc('x-todo-item label')[0]
            assert todo_item_label.text == 'First task'
    
    Source code(tar.gz)
    Source code(zip)
  • 1.1.0b0(Jun 20, 2019)

    Added:

    • Component.send_redirect has a new parameter push_state=True, when set it will do a push state in the front-end.
    • In the front-end you can use push_state(url) to trigger this in the front-end.
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0b0(Jun 18, 2019)

    Changed

    • Rename reactor.component.send_to_group to reactor.broadcast
    • Open the reactor channel after all HTML5 elements are created.
    • Improve reconnect timing logic.
    • When an event is sent to the back-end and the event originates from a form or inside of it, just the form data is serialized and sent to the back-end.

    Added

    • Django settings REACTOR_AUTO_BROADCAST = True will trigger broadcast updates when models instances are manipulated.
    • Add a component for authenticated user only and staff component: AuthComponent and StaffComponent
    • Add debounce function to send events to the backend.
    • Add directive reactor-once when an element is market with it, it will not be updated.
    • Add ReactorChannel.reconnect, closes the connection and reopens it.
    • Add public parameter to class inheritance, so you can decide if the element is exposed or not.
    Source code(tar.gz)
    Source code(zip)
  • 0.3.0b0(Jun 3, 2019)

    Changes

    Breaking changes:

    • Now the dispatching of incoming events from the front-end are not atomic transactions. You will have to put @atomic when you want something to be atomic. The reason for this is that some events don't have to deal at all with the database because they just change the local state of the component.

    Added:

    • reactor.broadcast(*names): this broadcasts the messages that you can subscribe to using Component.subscribe().
    • Component.send_redirect(url): now you can send a URL redirect to the front-end, when the component is being rendered for first time the redirect is rendered as a <meta http-equiv="refresh" content="0; url={{ url }}">, if the component is alive then the front-end controller executes the redirect.
    • AuthComponent, this component has a user attributed taken from the Component._context and on mount if the user is authenticated returns the True, if the user is not returns None and sends a redirect to settings.LOGIN_URL.
    • Serialization of arrays in the front-end, in case you have:
        <input name="query" value="q">
        <input name="persons[].name" value="a">
        <input name="persons[].name" value="b">
    

    This will be serialized as: {query: "q", persons: [{name: "a"}, {name: "b"}]}

    Room for improvement:

    • Proper documentation.
    • More testing.
    Source code(tar.gz)
    Source code(zip)
  • 0.2.1b0(May 26, 2019)

    • Adds to component component send_redirect(url). Example:
    def receive_save(self, name, **kwargs):
        self.instance.name = name
        self.instance.save()
        self.send_redirect(self.instance.get_absolute_url())
    
    • Components are now based on div and this can be redefined by the user overwriting the extends attribute. Example:
    class MySpecialInput(Component):
        extends = 'input'
    
    Source code(tar.gz)
    Source code(zip)
Owner
Eddy Ernesto del Valle Pino
Co-founder and CTO at Kaiko Systems
Eddy Ernesto del Valle Pino
Pyrin is an application framework built on top of Flask micro-framework to make life easier for developers who want to develop an enterprise application using Flask

Pyrin A rich, fast, performant and easy to use application framework to build apps using Flask on top of it. Pyrin is an application framework built o

Mohamad Nobakht 10 Jan 25, 2022
Containers And REST APIs Workshop

Containers & REST APIs Workshop Containers vs Virtual Machines Ferramentas Podman: https://podman.io/ Docker: https://www.docker.com/ IBM CLI: https:/

Vanderlei Munhoz 8 Dec 16, 2021
Otter is framework for creating microservices in Flask like fassion using RPC communication via message queue.

Otter Framework for microservices. Overview Otter is framework for creating microservices in Flask like fassion using RPC communication via message qu

Volodymyr Biloshytskyi 4 Mar 23, 2022
Pretty tornado wrapper for making lightweight REST API services

CleanAPI Pretty tornado wrapper for making lightweight REST API services Installation: pip install cleanapi Example: Project folders structure: . ├──

Vladimir Kirievskiy 26 Sep 11, 2022
WAZO REST API for the call management of the C4 infrastructure

wazo-router-calld wazo-router-calld provides REST API for the C4 infrastructure. Installing wazo-router-calld The server is already provided as a part

Wazo Platform 4 Dec 21, 2022
Phoenix LiveView but for Django

Reactor, a LiveView library for Django Reactor enables you to do something similar to Phoenix framework LiveView using Django Channels. What's in the

Eddy Ernesto del Valle Pino 526 Jan 02, 2023
aiohttp-ratelimiter is a rate limiter for the aiohttp.web framework.

aiohttp-ratelimiter aiohttp-ratelimiter is a rate limiter for the aiohttp.web fr

JGL Technologies 4 Dec 11, 2022
Bromelia-hss implements an HSS by using the Python micro framework Bromélia.

Bromélia HSS bromelia-hss is the second official implementation of a Diameter-based protocol application by using the Python micro framework Bromélia.

henriquemr 7 Nov 02, 2022
A high-level framework for building GitHub applications in Python.

A high-level framework for building GitHub applications in Python. Core Features Async Proper ratelimit handling Handles interactions for you (

Vish M 3 Apr 12, 2022
O SnakeG é um WSGI feito para suprir necessidadades de perfomance e segurança.

SnakeG O SnakeG é um WSGI feito para suprir necessidadades de perfomance e segurança. Veja o que o SnakeG possui: Multiprocessamento de requisições HT

Jaedson Silva 1 Jul 02, 2022
Full duplex RESTful API for your asyncio web apps

TBone TBone makes it easy to develop full-duplex RESTful APIs on top of your asyncio web application or webservice. It uses a nonblocking asynchronous

TBone Framework 37 Aug 07, 2022
The core of a service layer that integrates with the Pyramid Web Framework.

pyramid_services The core of a service layer that integrates with the Pyramid Web Framework. pyramid_services defines a pattern and helper methods for

Michael Merickel 78 Apr 15, 2022
Asita is a web application framework for python.

What is Asita ? Asita is a web application framework for python. It is designed to be easy to use and be more easy for javascript users to use python

Mattéo 4 Nov 16, 2021
Loan qualifier app - Loan Qualifier Application Built With Python

Loan Qualifier Application This program is designed to automate the discovery pr

Phil Hills 1 Jan 04, 2022
Mini Web Framework on MicroPython (Esp8266)

dupgee Dupgee is a mini web framework developed for micro-python(Tested on esp8266). Installation pip install dupgee Create Project dupgee create newp

ahmet kotan 38 Jul 25, 2022
bottle.py is a fast and simple micro-framework for python web-applications.

Bottle: Python Web Framework Bottle is a fast, simple and lightweight WSGI micro web-framework for Python. It is distributed as a single file module a

Bottle Micro Web Framework 7.8k Dec 31, 2022
Free and open source full-stack enterprise framework for agile development of secure database-driven web-based applications, written and programmable in Python.

Readme web2py is a free open source full-stack framework for rapid development of fast, scalable, secure and portable database-driven web-based applic

2k Dec 31, 2022
A tool for quickly creating REST/HATEOAS/Hypermedia APIs in python

ripozo Ripozo is a tool for building RESTful/HATEOAS/Hypermedia apis. It provides strong, simple, and fully qualified linking between resources, the a

Vertical Knowledge 198 Jan 07, 2023
A boilerplate Flask API for a Fullstack Project with some additional packages and configuration prebuilt. ⚙

Flask Boilerplate to quickly get started with production grade flask application with some additional packages and configuration prebuilt.

Yasser Tahiri 32 Dec 24, 2022
The comprehensive WSGI web application library.

Werkzeug werkzeug German noun: "tool". Etymology: werk ("work"), zeug ("stuff") Werkzeug is a comprehensive WSGI web application library. It began as

The Pallets Projects 6.2k Jan 01, 2023