Django LiveView

Django LiveView

Framework for creating Realtime SPAs using HTML over the Wire technology

Docs

Getting started

What is Django LiveView?

Django LiveView is a framework for creating Realtime SPAs using HTML over the Wire technology. It is inspired by Phoenix LiveView and it is built on top of Django Channels.

It allows you to create interactive web applications using only HTML, CSS and Python. JavaScript ONLY is used to capture events, send and receive strings over a WebSockets channel.

What are your superpowers?

Quickstart

All the steps are applied in a minimalist template.

See all steps

1. Install Django

Install Django, create a project and an app.

2. Install LiveView

Install django-liveview with pip

pip install django-liveview

3. Modify the configuration

Add liveview to your installed APPS

INSTALLED_APPS = [
    "daphne",
    "channels",
    "liveview",
]

Then indicate in which previously created App you want to implement LiveView.

LIVEVIEW_APPS = ["website"]

4. Migration

Execute the migrations so that the LiveView tables are generated.

python manage.py migrate

5. ASGI

Modify the ASGI file, asgi.py to add the LiveView routing. In this example it is assumed that settings.py is inside core, in your case it may be different.

import os
import django

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
django.setup()

from channels.auth import AuthMiddlewareStack
from django.core.asgi import get_asgi_application
from channels.security.websocket import AllowedHostsOriginValidator
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import re_path
from liveview.consumers import LiveViewConsumer


application = ProtocolTypeRouter(
    {
        # Django's ASGI application to handle traditional HTTP requests
        "http": get_asgi_application(),
        # WebSocket handler
        "websocket": AuthMiddlewareStack(
            AllowedHostsOriginValidator(
                URLRouter([re_path(r"^ws/liveview/$", LiveViewConsumer.as_asgi())])
            )
        ),
    }
)

6. Create your first Action

Place where the functions and logic of the business logic are stored. We will start by creating an action to generate a random number and print it.

Create inside your App a folder called actions, here will go all the actions for each page. Now we will create inside the folder a file named home.py.

# my-app/actions/home.py
from liveview.context_processors import get_global_context
from core import settings
from liveview.utils import (
    get_html,
    update_active_nav,
    enable_lang,
    loading,
)
from channels.db import database_sync_to_async
from django.templatetags.static import static
from django.urls import reverse
from django.utils.translation import gettext as _
from random import randint

template = "pages/home.html"

# Database

# Functions

async def get_context(consumer=None):
    context = get_global_context(consumer=consumer)
    # Update context
    context.update(
        {
            "url": settings.DOMAIN_URL + reverse("home"),
            "title": _("Home") + " | Home",
            "meta": {
                "description": _("Home page of the website"),
                "image": f"{settings.DOMAIN_URL}{static('img/seo/og-image.jpg')}",
            },
            "active_nav": "home",
            "page": template,
        }
    )
    return context


@enable_lang
@loading
async def send_page(consumer, client_data, lang=None):
    # Nav
    await update_active_nav(consumer, "home")
    # Main
    my_context = await get_context(consumer=consumer)
    html = await get_html(template, my_context)
    data = {
        "action": client_data["action"],
        "selector": "#main",
        "html": html,
    }
    data.update(my_context)
    await consumer.send_html(data)

async def random_number(consumer, client_data, lang=None):
    my_context = await get_context(consumer=consumer)
    data = {
        "action": client_data["action"],
        "selector": "#output-random-number",
        "html": randint(0, 10),
    }
    data.update(my_context)
    await consumer.send_html(data)

There are several points in the above code to keep in mind.

  • template is the name of the template that will be rendered.
  • get_context() is a function that returns a dictionary with the context of the page.
  • send_page() is the function that will be executed when the page is loaded.
  • random_number() is the function that will be executed when the button is clicked.

7. Create the base template

Now we will create the base template, which will be the one that will be rendered when the page is loaded.

Create a folder called templates, or use your template folder, inside your App and inside it create another folder called layouts. Now create a file called base.html.

{# my-app/templates/layouts/base.html #}
{% load static i18n %}
<!doctype html>{% get_current_language as CURRENT_LANGUAGE %}
<html lang="{{ CURRENT_LANGUAGE }}">
    <head>
        <meta charset="utf-8">
        <title>{{ title }}</title>
        <meta
            name="viewport"
            content="width=device-width, initial-scale=1.0, shrink-to-fit=no"
        >
        <meta
            name="description"
            content="{{ meta.description }}"
        >
        <meta
            property="og:image"
            content="{{ meta.image }}"
        >
	<script type="module" src="{% static 'js/main.js' %}" defer></script>
    </head>
    <body
		data-host="{{ request.get_host }}"
		data-debug="{{ DEBUG }}"
	>
            <section id="loading"></section>
	    <section id="notifications" class="notifications"></section>
	    <section id="no_connection"></section>
	    <div class="container">
		<header id="content-header">
		    {% include 'components/header.html' %}
		</header>
		<main id="main" class="main-container">{% include page %}</main>
		<footer id="content-footer">
		    {% include 'components/footer.html' %}
		</footer>
	    </div>
    </body>
</html>

In the future we will define main.js, a minimal JavaScript to connect the events and the WebSockets client.

8. Create the page template

We will create the home page template, which will be the one that will be rendered when the page is loaded.

Create a folder called pagesin your template folder and inside it create a file called home.html.

{# my-app/templates/pages/home.html #}
{% load static %}

<main data-controller="home">
    <p>
	<button data-action="click->home#randomNumber">Random number</button>
    </p>
    <h2 id="output-random-number"></h2>
</main>

As you can see, we have defined a button to launch the action of generating the random number (button) and the place where we will print the result (output-random-number).

9. Create frontend

Now we are going to create the frontend, the part where we will manage the JavaScript events and invoke the actions.

Download assets and unzip it in your static folder. You will be left with the following route: /static/js/.

10. Create View

We will create the view that will render the page for the first time (like Server Side Rendering). The rest of the times will be rendered dynamically (like Single Page Application).

In a normal Django application we would create a view, views.py, similar to the following:

# my-app/views.py
from django.shortcuts import render

# Create your views here.
def home(request):
    return render(request, "pages/home.html")

With LiveView, on the other hand, you will have the following structure.

# my-app/views.py
from django.shortcuts import render
from .actions.home import get_context as get_home_context

from liveview.utils import get_html

async def home(request):
    return render(request, "layouts/base.html", await get_home_context())

11. Create URL

Finally, we will create the URL that will render the page.

# my-app/urls.py
from django.urls import path

from .views import home

urlpatterns = [
    path("", home, name="home"),
]

Result

Tutorials

A list of tutorials with examples.

Make a blog

Below we will make a simple blog with classic features:

  • A list with posts
  • Single page post
  • Controls to navegate between list posts and singles
  • Pagination
  • Search

If you want to include a system commentary, read the next tutorial.

Creating models

Before starting, we will create the models that we will use in the blog.

Adding fake data

Preparing views (SSR)

Making templates

Including actions

Adding the feature: infinite scroll

Adding the feature: search

Add a feedback system

Creating models

Adding fake data

Preparing views (SSR)

Making templates

Including actions

Getting data

Showing

Source code