mirror of
https://github.com/Django-LiveView/docs.git
synced 2025-11-25 06:05:17 +01:00
Some checks failed
Gitea Actions Deploy / deploy (push) Has been cancelled
Added documentation for the new data-liveview-debounce attribute introduced in django-liveview 2.1.0. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
995 lines
27 KiB
Org Mode
995 lines
27 KiB
Org Mode
* Home
|
|
:PROPERTIES:
|
|
:ONE: one-custom-default-home
|
|
:CUSTOM_ID: /
|
|
:TITLE:
|
|
:DESCRIPTION: Build real-time, reactive interfaces with Django using WebSockets — write Python, not JavaScript.
|
|
:NAVIGATOR-ACTIVE: home
|
|
:END:
|
|
|
|
** What is HTML over the Wire?
|
|
|
|
HTML over the Wire, or HTML over WebSockets, is a strategy for creating real-time SPAs by establishing a WebSocket connection between a client and server. It allows JavaScript to request actions—its only responsibility is to handle events—while the backend handles the business logic and renders HTML. This means you can create dynamic pages without reloading, without AJAX or APIs. This technology provides a secure, stable, and low-latency connection for real-time web applications.
|
|
|
|
#+ATTR_HTML: :class center-block image image--home
|
|
[[#/img/step-1.avif][Architecture send]]
|
|
|
|
#+ATTR_HTML: :class center-block image image--home
|
|
[[#/img/step-2.avif][Architecture receive]]
|
|
|
|
** What is Django LiveView? 🚀
|
|
|
|
Django LiveView is a framework for creating real-time, interactive web applications entirely in Python 🐍, inspired by [[https://hexdocs.pm/phoenix_live_view/][Phoenix LiveView]] and [[https://laravel-livewire.com/][Laravel Livewire]]. It is built on top of Django Channels.
|
|
|
|
Build rich, dynamic user experiences ✨ with server-rendered HTML without writing a single line of JavaScript. Perfect for Django developers who want real-time features ⚡ without the complexity of a separate frontend framework.
|
|
|
|
Let's illustrate with an example. I want to print article number 2.
|
|
|
|
1. A WebSocket connection (a channel) is established between the client and the server.
|
|
|
|
2. JavaScript sends a message via WebSocket to the server (Django).
|
|
|
|
#+ATTR_HTML: :class center-block image image--home
|
|
[[#/img/step-3.avif][Send string]]
|
|
|
|
3. Django interprets the message and renders the HTML of the article through the template system and the database.
|
|
|
|
4. Django sends the HTML to JavaScript via the channel and specifies which selector to embed it in.
|
|
|
|
#+ATTR_HTML: :class center-block image image--home
|
|
[[#/img/step-4.avif][Send JSON]]
|
|
|
|
5. JavaScript renders the received HTML in the indicated selector.
|
|
|
|
#+ATTR_HTML: :class center-block image image--home
|
|
[[#/img/step-5.avif][Place HTML]]
|
|
|
|
The same process is repeated for each action, such as clicking a button, submitting a form, etc.
|
|
|
|
** What are your superpowers? 💪
|
|
|
|
- 🎯 **Create SPAs without using APIs** — No REST or GraphQL needed
|
|
- 🎨 **Uses Django's template system** to render the frontend (without JavaScript frameworks)
|
|
- 🐍 **Logic stays in Python** — No split between backend and frontend
|
|
- 🛠️ **Use all of Django's tools** — ORM, forms, authentication, admin, etc.
|
|
- ⚡ **Everything is asynchronous by default** — Built on Django Channels
|
|
- 📚 **Zero learning curve** — If you know Python and Django, you're ready
|
|
- 🔄 **Real-time by design** — All interactions happen over WebSockets
|
|
- 🔋 **Batteries included** — JavaScript assets bundled, automatic reconnection with exponential backoff
|
|
- 💡 **Type hints** and modern Python (3.10+)
|
|
- 📡 **Broadcast support** for multi-user real-time updates
|
|
- 🔐 **Middleware system** for authentication and authorization
|
|
|
|
Are you ready to create your first real-time SPA? Let's go to the [[#/tutorial/][Tutorial]].
|
|
|
|
* Install
|
|
:PROPERTIES:
|
|
:ONE: one-custom-default-doc
|
|
:CUSTOM_ID: /docs/install/
|
|
:TITLE: Install
|
|
:DESCRIPTION: Install Django LiveView.
|
|
:NAVIGATOR-ACTIVE: docs
|
|
:END:
|
|
|
|
** Requirements
|
|
|
|
- Python 3.10+
|
|
- Django 4.2+
|
|
- Redis (for Channels layer)
|
|
- Channels 4.0+
|
|
|
|
** Installation
|
|
|
|
Install Django LiveView with pip:
|
|
|
|
#+BEGIN_SRC sh
|
|
pip install django-liveview
|
|
#+END_SRC
|
|
|
|
** Configure Django
|
|
|
|
Add to your ~settings.py~:
|
|
|
|
#+BEGIN_SRC python
|
|
# settings.py
|
|
INSTALLED_APPS = [
|
|
"daphne", # Must be first for ASGI support
|
|
"channels",
|
|
"liveview",
|
|
# ... your other apps
|
|
]
|
|
|
|
# ASGI configuration
|
|
ASGI_APPLICATION = "your_project.asgi.application"
|
|
|
|
# Configure Channels with Redis
|
|
CHANNEL_LAYERS = {
|
|
"default": {
|
|
"BACKEND": "channels_redis.core.RedisChannelLayer",
|
|
"CONFIG": {
|
|
"hosts": [("127.0.0.1", 6379)],
|
|
},
|
|
},
|
|
}
|
|
#+END_SRC
|
|
|
|
** Setup ASGI routing
|
|
|
|
Create or update ~asgi.py~:
|
|
|
|
#+BEGIN_SRC python
|
|
# asgi.py
|
|
import os
|
|
from django.core.asgi import get_asgi_application
|
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
|
from channels.auth import AuthMiddlewareStack
|
|
from channels.security.websocket import AllowedHostsOriginValidator
|
|
from liveview.routing import get_liveview_urlpatterns
|
|
|
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "your_project.settings")
|
|
|
|
application = ProtocolTypeRouter({
|
|
"http": get_asgi_application(),
|
|
"websocket": AllowedHostsOriginValidator(
|
|
AuthMiddlewareStack(
|
|
URLRouter(
|
|
get_liveview_urlpatterns()
|
|
)
|
|
)
|
|
),
|
|
})
|
|
#+END_SRC
|
|
|
|
** Add JavaScript to your base template
|
|
|
|
#+BEGIN_SRC html
|
|
<!-- templates/base.html -->
|
|
{% load static %}
|
|
<!DOCTYPE html>
|
|
<html lang="en" data-room="{% if request.user.is_authenticated %}{{ request.user.id }}{% else %}anonymous{% endif %}">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>{% block title %}My Site{% endblock %}</title>
|
|
</head>
|
|
<body data-controller="page">
|
|
{% block content %}{% endblock %}
|
|
|
|
<!-- Django LiveView JavaScript -->
|
|
<script src="{% static 'liveview/liveview.min.js' %}" defer></script>
|
|
</body>
|
|
</html>
|
|
#+END_SRC
|
|
|
|
**Important attributes:**
|
|
- ~data-room~ on ~<html>~ — unique identifier for WebSocket room (user-specific or shared)
|
|
- ~data-controller="page"~ on ~<body>~ — activates the Stimulus controller
|
|
|
|
We strongly recommend that you follow the [[#/tutorial/][Tutorial]] to see the installation in action.
|
|
|
|
* Handlers
|
|
:PROPERTIES:
|
|
:ONE: one-custom-default-doc
|
|
:CUSTOM_ID: /docs/handlers/
|
|
:TITLE: Handlers
|
|
:DESCRIPTION: LiveView handlers of Django LiveView.
|
|
:NAVIGATOR-ACTIVE: docs
|
|
:END:
|
|
|
|
Handlers are Python functions that respond to WebSocket messages from the client. They contain your business logic and can render HTML, update the database, broadcast to multiple users, and more.
|
|
|
|
** Creating Handlers
|
|
|
|
Use the ~@liveview_handler~ decorator to register functions that can be called from the client:
|
|
|
|
#+BEGIN_SRC python
|
|
from liveview import liveview_handler, send
|
|
from django.template.loader import render_to_string
|
|
|
|
@liveview_handler("say_hello")
|
|
def say_hello(consumer, content):
|
|
"""
|
|
Args:
|
|
consumer: WebSocket consumer instance
|
|
content: dict with:
|
|
- function: str - the function name
|
|
- data: dict - custom data from data-data-* attributes
|
|
- form: dict - form input values
|
|
- lang: str - current language
|
|
- room: str - room identifier
|
|
"""
|
|
name = content.get("form", {}).get("name", "World")
|
|
|
|
html = render_to_string("hello_message.html", {
|
|
"message": f"Hello, {name}!"
|
|
})
|
|
|
|
send(consumer, {
|
|
"target": "#greeting",
|
|
"html": html
|
|
})
|
|
#+END_SRC
|
|
|
|
** Auto-discovery
|
|
|
|
Django LiveView automatically discovers handlers in ~liveview_components/~ directories within your installed apps:
|
|
|
|
#+BEGIN_SRC
|
|
my_app/
|
|
├── liveview_components/
|
|
│ ├── __init__.py
|
|
│ ├── users.py
|
|
│ ├── posts.py
|
|
│ └── comments.py
|
|
#+END_SRC
|
|
|
|
Handlers are loaded on startup with this output:
|
|
#+BEGIN_SRC
|
|
✓ Imported: my_app.liveview_components.users
|
|
✓ Imported: my_app.liveview_components.posts
|
|
✓ Imported: my_app.liveview_components.comments
|
|
#+END_SRC
|
|
|
|
** Using the send() function
|
|
|
|
The ~send()~ function sends data back to the client with many options:
|
|
|
|
*** Replace HTML (Basic Update)
|
|
|
|
#+BEGIN_SRC python
|
|
@liveview_handler("update_content")
|
|
def update_content(consumer, content):
|
|
send(consumer, {
|
|
"target": "#my-element",
|
|
"html": "<p>New content</p>"
|
|
})
|
|
#+END_SRC
|
|
|
|
*** Append HTML
|
|
|
|
#+BEGIN_SRC python
|
|
@liveview_handler("add_item")
|
|
def add_item(consumer, content):
|
|
send(consumer, {
|
|
"target": "#items-list",
|
|
"html": "<li>New item</li>",
|
|
"append": True # Adds to the end
|
|
})
|
|
#+END_SRC
|
|
|
|
*** Remove Element
|
|
|
|
#+BEGIN_SRC python
|
|
@liveview_handler("delete_item")
|
|
def delete_item(consumer, content):
|
|
item_id = content["data"]["id"]
|
|
# Delete from database...
|
|
|
|
send(consumer, {
|
|
"target": f"#item-{item_id}",
|
|
"remove": True
|
|
})
|
|
#+END_SRC
|
|
|
|
*** Update URL and Title
|
|
|
|
#+BEGIN_SRC python
|
|
@liveview_handler("navigate")
|
|
def navigate(consumer, content):
|
|
send(consumer, {
|
|
"target": "#content",
|
|
"html": render_to_string("new_page.html"),
|
|
"url": "/new-page/",
|
|
"title": "New Page Title"
|
|
})
|
|
#+END_SRC
|
|
|
|
*** Scroll Management
|
|
|
|
#+BEGIN_SRC python
|
|
@liveview_handler("show_section")
|
|
def show_section(consumer, content):
|
|
send(consumer, {
|
|
"target": "#content",
|
|
"html": render_to_string("section.html"),
|
|
"scroll": "#section-2" # Smooth scroll to element
|
|
})
|
|
|
|
@liveview_handler("back_to_top")
|
|
def back_to_top(consumer, content):
|
|
send(consumer, {
|
|
"target": "#content",
|
|
"html": render_to_string("content.html"),
|
|
"scrollTop": True # Scroll to top of page
|
|
})
|
|
#+END_SRC
|
|
|
|
* Frontend Integration
|
|
:PROPERTIES:
|
|
:ONE: one-custom-default-doc
|
|
:CUSTOM_ID: /docs/frontend/
|
|
:TITLE: Frontend Integration
|
|
:DESCRIPTION: How to call handlers from the frontend.
|
|
:NAVIGATOR-ACTIVE: docs
|
|
:END:
|
|
|
|
The frontend is responsible for capturing events and sending messages over WebSocket. No logic, rendering, or state is in the frontend—the backend does all the work.
|
|
|
|
Django LiveView uses [[https://stimulus.hotwired.dev/][Stimulus]] to manage DOM events and avoid collisions. The JavaScript assets are bundled within the package.
|
|
|
|
** Calling Handlers
|
|
|
|
To call a handler from a button click:
|
|
|
|
#+BEGIN_SRC html
|
|
<button
|
|
data-liveview-function="say_hello"
|
|
data-action="click->page#run">
|
|
Say Hello
|
|
</button>
|
|
#+END_SRC
|
|
|
|
- ~data-liveview-function~ — The handler name registered with ~@liveview_handler~
|
|
- ~data-action~ — Stimulus action (event->controller#action)
|
|
|
|
** Sending Form Data
|
|
|
|
All form fields are automatically extracted and sent in ~content["form"]~:
|
|
|
|
#+BEGIN_SRC html
|
|
<div>
|
|
<input type="text" name="name" placeholder="Enter your name">
|
|
<button
|
|
data-liveview-function="say_hello"
|
|
data-action="click->page#run">
|
|
Submit
|
|
</button>
|
|
</div>
|
|
#+END_SRC
|
|
|
|
** Sending Custom Data
|
|
|
|
Use ~data-data-*~ attributes to send additional data:
|
|
|
|
#+BEGIN_SRC html
|
|
<button
|
|
data-liveview-function="open_modal"
|
|
data-data-modal-id="123"
|
|
data-data-user-id="456"
|
|
data-action="click->page#run">
|
|
Open Modal
|
|
</button>
|
|
#+END_SRC
|
|
|
|
Access in Python:
|
|
|
|
#+BEGIN_SRC python
|
|
@liveview_handler("open_modal")
|
|
def open_modal(consumer, content):
|
|
data = content.get("data", {})
|
|
modal_id = data.get("modalId") # from modal-id
|
|
user_id = data.get("userId") # from user-id
|
|
#+END_SRC
|
|
|
|
** Stimulus Actions Reference
|
|
|
|
Available Stimulus actions:
|
|
|
|
- ~data-action="click->page#run"~ — Execute LiveView function on click
|
|
- ~data-action="input->page#run"~ — Execute on input change (real-time)
|
|
- ~data-action="submit->page#run"~ — Execute on form submit
|
|
- ~data-action="change->page#run"~ — Execute on change event
|
|
- ~data-action="blur->page#run"~ — Execute when element loses focus
|
|
- ~data-action="page#stop"~ — Stop event propagation
|
|
|
|
* Forms
|
|
:PROPERTIES:
|
|
:ONE: one-custom-default-doc
|
|
:CUSTOM_ID: /docs/forms/
|
|
:TITLE: Forms
|
|
:DESCRIPTION: Form handling in Django LiveView.
|
|
:NAVIGATOR-ACTIVE: docs
|
|
:END:
|
|
|
|
Django LiveView works seamlessly with Django forms. Form data is sent via WebSocket instead of HTTP.
|
|
|
|
** Form Handling Example
|
|
|
|
Python handler:
|
|
|
|
#+BEGIN_SRC python
|
|
@liveview_handler("submit_contact")
|
|
def submit_contact(consumer, content):
|
|
from .forms import ContactForm
|
|
|
|
form = ContactForm(content["form"])
|
|
|
|
if form.is_valid():
|
|
# Save to database
|
|
contact = form.save()
|
|
|
|
# Show success message
|
|
html = render_to_string("contact_success.html", {
|
|
"message": "Thank you! We'll be in touch."
|
|
})
|
|
else:
|
|
# Show form with errors
|
|
html = render_to_string("contact_form.html", {
|
|
"form": form
|
|
})
|
|
|
|
send(consumer, {
|
|
"target": "#contact-container",
|
|
"html": html
|
|
})
|
|
#+END_SRC
|
|
|
|
HTML template:
|
|
|
|
#+BEGIN_SRC html
|
|
<div id="contact-container">
|
|
<form>
|
|
<input type="text" name="name" placeholder="Name" required>
|
|
<input type="email" name="email" placeholder="Email" required>
|
|
<textarea name="message" placeholder="Message" required></textarea>
|
|
|
|
<button
|
|
data-liveview-function="submit_contact"
|
|
data-action="click->page#run"
|
|
type="button">
|
|
Submit
|
|
</button>
|
|
</form>
|
|
</div>
|
|
#+END_SRC
|
|
|
|
** Real-time Validation
|
|
|
|
#+BEGIN_SRC python
|
|
@liveview_handler("validate_field")
|
|
def validate_field(consumer, content):
|
|
field_name = content["data"]["field"]
|
|
field_value = content["form"].get(field_name, "")
|
|
|
|
# Validate
|
|
error = None
|
|
if field_name == "email" and "@" not in field_value:
|
|
error = "Invalid email address"
|
|
elif field_name == "name" and len(field_value) < 3:
|
|
error = "Name must be at least 3 characters"
|
|
|
|
# Show error or success
|
|
html = f'<span class="{"error" if error else "success"}">{error or "✓"}</span>'
|
|
|
|
send(consumer, {
|
|
"target": f"#error-{field_name}",
|
|
"html": html
|
|
})
|
|
#+END_SRC
|
|
|
|
#+BEGIN_SRC html
|
|
<input
|
|
type="text"
|
|
name="email"
|
|
data-liveview-function="validate_field"
|
|
data-data-field="email"
|
|
data-action="blur->page#run">
|
|
<span id="error-email"></span>
|
|
#+END_SRC
|
|
|
|
* Broadcasting
|
|
:PROPERTIES:
|
|
:ONE: one-custom-default-doc
|
|
:CUSTOM_ID: /docs/broadcasting/
|
|
:TITLE: Broadcasting
|
|
:DESCRIPTION: Broadcasting updates to multiple users.
|
|
:NAVIGATOR-ACTIVE: docs
|
|
:END:
|
|
|
|
Send updates to all connected clients using the ~broadcast~ parameter:
|
|
|
|
** Simple Broadcast
|
|
|
|
#+BEGIN_SRC python
|
|
@liveview_handler("notify_all")
|
|
def notify_all(consumer, content):
|
|
message = content["form"]["message"]
|
|
|
|
html = render_to_string("notification.html", {
|
|
"message": message
|
|
})
|
|
|
|
send(consumer, {
|
|
"target": "#notifications",
|
|
"html": html,
|
|
"append": True
|
|
}, broadcast=True) # Sends to ALL connected users
|
|
#+END_SRC
|
|
|
|
** Background Thread Broadcast with Auto-removal
|
|
|
|
#+BEGIN_SRC python
|
|
from threading import Thread
|
|
from time import sleep
|
|
from uuid import uuid4
|
|
|
|
@liveview_handler("send_notification")
|
|
def send_notification(consumer, content):
|
|
notification_id = str(uuid4().hex)
|
|
message = "New update available!"
|
|
|
|
def broadcast_notification():
|
|
# Send notification
|
|
html = render_to_string("notification.html", {
|
|
"id": notification_id,
|
|
"message": message
|
|
})
|
|
|
|
send(consumer, {
|
|
"target": "#notifications",
|
|
"html": html,
|
|
"append": True
|
|
}, broadcast=True)
|
|
|
|
# Remove after 5 seconds
|
|
sleep(5)
|
|
send(consumer, {
|
|
"target": f"#notification-{notification_id}",
|
|
"remove": True
|
|
}, broadcast=True)
|
|
|
|
Thread(target=broadcast_notification).start()
|
|
#+END_SRC
|
|
|
|
* Advanced Features
|
|
:PROPERTIES:
|
|
:ONE: one-custom-default-doc
|
|
:CUSTOM_ID: /docs/advanced/
|
|
:TITLE: Advanced Features
|
|
:DESCRIPTION: Advanced features of Django LiveView.
|
|
:NAVIGATOR-ACTIVE: docs
|
|
:END:
|
|
|
|
** Intersection Observer (Infinite Scroll)
|
|
|
|
Trigger functions when elements enter or exit the viewport:
|
|
|
|
#+BEGIN_SRC python
|
|
ITEMS_PER_PAGE = 10
|
|
|
|
@liveview_handler("load_more")
|
|
def load_more(consumer, content):
|
|
page = int(content["data"].get("page", 1))
|
|
|
|
# Fetch items
|
|
start = (page - 1) * ITEMS_PER_PAGE
|
|
end = start + ITEMS_PER_PAGE
|
|
items = Item.objects.all()[start:end]
|
|
is_last_page = end >= Item.objects.count()
|
|
|
|
# Append items to list
|
|
send(consumer, {
|
|
"target": "#items-list",
|
|
"html": render_to_string("items_partial.html", {
|
|
"items": items
|
|
}),
|
|
"append": True
|
|
})
|
|
|
|
# Update or remove intersection observer trigger
|
|
if is_last_page:
|
|
html = ""
|
|
else:
|
|
html = render_to_string("load_trigger.html", {
|
|
"next_page": page + 1
|
|
})
|
|
|
|
send(consumer, {
|
|
"target": "#load-more-trigger",
|
|
"html": html
|
|
})
|
|
#+END_SRC
|
|
|
|
HTML template:
|
|
|
|
#+BEGIN_SRC html
|
|
<!-- load_trigger.html -->
|
|
<div
|
|
data-liveview-intersect-appear="load_more"
|
|
data-data-page="{{ next_page }}"
|
|
data-liveview-intersect-threshold="200">
|
|
<p>Loading more...</p>
|
|
</div>
|
|
#+END_SRC
|
|
|
|
**Attributes:**
|
|
- ~data-liveview-intersect-appear="function_name"~ — Call when element appears
|
|
- ~data-liveview-intersect-disappear="function_name"~ — Call when element disappears
|
|
- ~data-liveview-intersect-threshold="200"~ — Trigger 200px before entering viewport (default: 0)
|
|
|
|
** Auto-focus
|
|
|
|
Automatically focus elements after rendering:
|
|
|
|
#+BEGIN_SRC html
|
|
<input
|
|
type="text"
|
|
name="title"
|
|
value="{{ item.title }}"
|
|
data-liveview-focus="true">
|
|
#+END_SRC
|
|
|
|
** Init Functions
|
|
|
|
Execute functions when elements are first rendered:
|
|
|
|
#+BEGIN_SRC html
|
|
<div
|
|
data-liveview-init="init_counter"
|
|
data-data-counter-id="1"
|
|
data-data-initial-value="0">
|
|
<span id="counter-1-value"></span>
|
|
</div>
|
|
#+END_SRC
|
|
|
|
** Debounce
|
|
|
|
Reduce server calls by adding a delay before sending requests. Perfect for search inputs and real-time validation:
|
|
|
|
#+BEGIN_SRC html
|
|
<input
|
|
type="search"
|
|
name="search"
|
|
data-liveview-function="search_articles"
|
|
data-liveview-debounce="500"
|
|
data-action="input->page#run"
|
|
placeholder="Search articles...">
|
|
#+END_SRC
|
|
|
|
The ~data-liveview-debounce="500"~ attribute waits 500ms after the user stops typing before sending the request. This dramatically reduces server load and provides a better user experience.
|
|
|
|
**Example: Real-time search with debounce**
|
|
|
|
#+BEGIN_SRC python
|
|
from liveview import liveview_handler, send
|
|
from django.template.loader import render_to_string
|
|
|
|
@liveview_handler("search_articles")
|
|
def search_articles(consumer, content):
|
|
query = content["form"]["search"]
|
|
articles = Article.objects.filter(title__icontains=query)
|
|
|
|
html = render_to_string("search_results.html", {
|
|
"articles": articles
|
|
})
|
|
|
|
send(consumer, {
|
|
"target": "#search-results",
|
|
"html": html
|
|
})
|
|
#+END_SRC
|
|
|
|
Without debounce, typing "python" would send 6 requests (one per letter). With ~data-liveview-debounce="500"~, it sends only 1 request after the user stops typing for 500ms.
|
|
|
|
** Middleware System
|
|
|
|
Add middleware to run before handlers for authentication, logging, or rate limiting:
|
|
|
|
#+BEGIN_SRC python
|
|
from liveview import liveview_registry, send
|
|
|
|
def auth_middleware(consumer, content, function_name):
|
|
"""Check if user is authenticated before running handler"""
|
|
user = consumer.scope.get("user")
|
|
|
|
if not user or not user.is_authenticated:
|
|
send(consumer, {
|
|
"target": "#error",
|
|
"html": "<p>You must be logged in</p>"
|
|
})
|
|
return False # Cancel handler execution
|
|
|
|
return True # Continue to handler
|
|
|
|
def logging_middleware(consumer, content, function_name):
|
|
"""Log all handler calls"""
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
user = consumer.scope.get("user")
|
|
logger.info(f"Handler '{function_name}' called by {user}")
|
|
|
|
return True # Continue to handler
|
|
|
|
# Register middleware
|
|
liveview_registry.add_middleware(auth_middleware)
|
|
liveview_registry.add_middleware(logging_middleware)
|
|
#+END_SRC
|
|
|
|
* Internationalization
|
|
:PROPERTIES:
|
|
:ONE: one-custom-default-doc
|
|
:CUSTOM_ID: /docs/internationalization/
|
|
:TITLE: Internationalization
|
|
:DESCRIPTION: Multi-language support in Django LiveView.
|
|
:NAVIGATOR-ACTIVE: docs
|
|
:END:
|
|
|
|
Django LiveView automatically passes the current language to handlers:
|
|
|
|
#+BEGIN_SRC python
|
|
from django.utils import translation
|
|
|
|
@liveview_handler("show_content")
|
|
def show_content(consumer, content):
|
|
# Get language from WebSocket message
|
|
lang = content.get("lang", "en")
|
|
|
|
# Activate language for this context
|
|
translation.activate(lang)
|
|
|
|
try:
|
|
html = render_to_string("content.html", {
|
|
"title": _("Welcome"),
|
|
"message": _("This content is in your language")
|
|
})
|
|
|
|
send(consumer, {
|
|
"target": "#content",
|
|
"html": html
|
|
})
|
|
finally:
|
|
# Always deactivate to avoid side effects
|
|
translation.deactivate()
|
|
#+END_SRC
|
|
|
|
The language is automatically detected from the ~<html>~ tag:
|
|
|
|
#+BEGIN_SRC html
|
|
{% load static i18n %}
|
|
<!doctype html>{% get_current_language as CURRENT_LANGUAGE %}
|
|
<html lang="{{ CURRENT_LANGUAGE }}">
|
|
#+END_SRC
|
|
|
|
* FAQ
|
|
:PROPERTIES:
|
|
:ONE: one-custom-default-doc
|
|
:CUSTOM_ID: /docs/faq/
|
|
:TITLE: FAQ
|
|
:DESCRIPTION: Frequently asked questions about Django LiveView.
|
|
:NAVIGATOR-ACTIVE: docs
|
|
:END:
|
|
|
|
** Do I need to know JavaScript to use Django LiveView?
|
|
|
|
No, you don't need to. You can create SPAs without using APIs, without JavaScript, and without learning anything new. If you know Python, you know how to use Django LiveView.
|
|
|
|
** Can I use JavaScript?
|
|
|
|
Yes, you can. You can use JavaScript to enhance your application, but it's not required for basic functionality.
|
|
|
|
** Can I use Django's native tools?
|
|
|
|
Of course. You can still use all of Django's native tools, such as its ORM, forms, authentication, admin, etc.
|
|
|
|
** Do I need to use React, Vue, Angular or any other frontend framework?
|
|
|
|
No. All logic, rendering and state is in the backend.
|
|
|
|
** Can I use Django REST Framework or GraphQL?
|
|
|
|
Yes, you can use both alongside Django LiveView.
|
|
|
|
** What's the difference between v0.1.0 and v2.0.0?
|
|
|
|
v2.0.0 is a complete rewrite with a much simpler API:
|
|
- Module name changed from ~django_liveview~ to ~liveview~ for cleaner imports
|
|
- Simpler decorator-based API with ~@liveview_handler~
|
|
- Built-in auto-discovery of handlers
|
|
- JavaScript assets bundled within the package
|
|
- More comprehensive documentation
|
|
|
|
** Who finances the project?
|
|
|
|
This project is maintained by Andros Fenollosa in his free time. If you want to support the project, you can become a [[https://liberapay.com/androsfenollosa/][patron]].
|
|
|
|
* Tutorial
|
|
:PROPERTIES:
|
|
:ONE: one-custom-default-tutorials
|
|
:CUSTOM_ID: /tutorial/
|
|
:TITLE: Tutorial
|
|
:DESCRIPTION: Get started with Django LiveView the easy way.
|
|
:NAVIGATOR-ACTIVE: tutorial
|
|
:END:
|
|
|
|
Welcome to the Tutorial guide. Here you will learn how to create your first real-time SPA using Django LiveView. I assume you have a basic understanding of Django and Python.
|
|
|
|
All the steps are applied in a [[https://github.com/Django-LiveView/minimal-template][minimalist template]].
|
|
|
|
** Step 1: Installation
|
|
|
|
#+BEGIN_SRC sh
|
|
pip install django-liveview
|
|
#+END_SRC
|
|
|
|
** Step 2: Configure Django
|
|
|
|
Add to your ~settings.py~:
|
|
|
|
#+BEGIN_SRC python
|
|
# settings.py
|
|
INSTALLED_APPS = [
|
|
"daphne", # Must be first for ASGI support
|
|
"channels",
|
|
"liveview",
|
|
# ... your other apps
|
|
]
|
|
|
|
# ASGI configuration
|
|
ASGI_APPLICATION = "your_project.asgi.application"
|
|
|
|
# Configure Channels with Redis
|
|
CHANNEL_LAYERS = {
|
|
"default": {
|
|
"BACKEND": "channels_redis.core.RedisChannelLayer",
|
|
"CONFIG": {
|
|
"hosts": [("127.0.0.1", 6379)],
|
|
},
|
|
},
|
|
}
|
|
#+END_SRC
|
|
|
|
** Step 3: Setup ASGI routing
|
|
|
|
Create or update ~asgi.py~:
|
|
|
|
#+BEGIN_SRC python
|
|
# asgi.py
|
|
import os
|
|
from django.core.asgi import get_asgi_application
|
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
|
from channels.auth import AuthMiddlewareStack
|
|
from channels.security.websocket import AllowedHostsOriginValidator
|
|
from liveview.routing import get_liveview_urlpatterns
|
|
|
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "your_project.settings")
|
|
|
|
application = ProtocolTypeRouter({
|
|
"http": get_asgi_application(),
|
|
"websocket": AllowedHostsOriginValidator(
|
|
AuthMiddlewareStack(
|
|
URLRouter(
|
|
get_liveview_urlpatterns()
|
|
)
|
|
)
|
|
),
|
|
})
|
|
#+END_SRC
|
|
|
|
** Step 4: Add JavaScript to your base template
|
|
|
|
#+BEGIN_SRC html
|
|
<!-- templates/base.html -->
|
|
{% load static %}
|
|
<!DOCTYPE html>
|
|
<html lang="en" data-room="{% if request.user.is_authenticated %}{{ request.user.id }}{% else %}anonymous{% endif %}">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>{% block title %}My Site{% endblock %}</title>
|
|
</head>
|
|
<body data-controller="page">
|
|
{% block content %}{% endblock %}
|
|
|
|
<!-- Django LiveView JavaScript -->
|
|
<script src="{% static 'liveview/liveview.min.js' %}" defer></script>
|
|
</body>
|
|
</html>
|
|
#+END_SRC
|
|
|
|
** Step 5: Create your first LiveView handler
|
|
|
|
Create ~app/liveview_components/hello.py~:
|
|
|
|
#+BEGIN_SRC python
|
|
# app/liveview_components/hello.py
|
|
from liveview import liveview_handler, send
|
|
from django.template.loader import render_to_string
|
|
|
|
@liveview_handler("say_hello")
|
|
def say_hello(consumer, content):
|
|
"""Handle 'say_hello' function from client"""
|
|
name = content.get("form", {}).get("name", "World")
|
|
|
|
html = render_to_string("hello_message.html", {
|
|
"message": f"Hello, {name}!"
|
|
})
|
|
|
|
send(consumer, {
|
|
"target": "#greeting",
|
|
"html": html
|
|
})
|
|
#+END_SRC
|
|
|
|
Create the template ~templates/hello_message.html~:
|
|
|
|
#+BEGIN_SRC html
|
|
<h1>{{ message }}</h1>
|
|
#+END_SRC
|
|
|
|
** Step 6: Use it in your page
|
|
|
|
#+BEGIN_SRC html
|
|
<!-- templates/hello_page.html -->
|
|
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<div>
|
|
<input type="text" name="name" placeholder="Enter your name">
|
|
<button
|
|
data-liveview-function="say_hello"
|
|
data-action="click->page#run">
|
|
Say Hello
|
|
</button>
|
|
|
|
<div id="greeting">
|
|
<h1>Hello, World!</h1>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
#+END_SRC
|
|
|
|
** Step 7: Run your project
|
|
|
|
#+BEGIN_SRC sh
|
|
# Run Django with Daphne (ASGI server)
|
|
python manage.py runserver
|
|
#+END_SRC
|
|
|
|
That's it! Click the button and see real-time updates. 🎉
|
|
|
|
You can also interact with the [[https://django-liveview-demo-minimal-template.andros.dev/][online demo]].
|
|
|
|
Congratulations! You have created your first real-time SPA using Django LiveView.
|
|
|
|
* Source code
|
|
:PROPERTIES:
|
|
:ONE: one-custom-default-page
|
|
:CUSTOM_ID: /source-code/
|
|
:TITLE: Source code
|
|
:DESCRIPTION: List of all related source code.
|
|
:NAVIGATOR-ACTIVE: source code
|
|
:END:
|
|
|
|
You can find all the source code in the following repositories:
|
|
|
|
- [[https://github.com/Django-LiveView/liveview][LiveView]]: Source code of the Django framework published on PyPI as django-liveview
|
|
- [[https://github.com/Django-LiveView/docs][Website and Docs]]: All documentation, including this page
|
|
- Templates
|
|
- [[https://github.com/Django-LiveView/starter-template][Starter]]: Check all the features of Django LiveView
|
|
- [[https://github.com/Django-LiveView/minimal-template][Minimal]]: The minimal template to get started
|
|
- Demos
|
|
- [[https://github.com/Django-LiveView/demo-snake][Snake]]: The classic game of Snake
|
|
|
|
* Books
|
|
:PROPERTIES:
|
|
:ONE: one-custom-default-page
|
|
:CUSTOM_ID: /books/
|
|
:TITLE:
|
|
:DESCRIPTION: Books about Django LiveView.
|
|
:NAVIGATOR-ACTIVE: books
|
|
:END:
|
|
|
|
There are no books specifically about Django LiveView yet, but you can find books about Django working with HTML over the Wire technology.
|
|
|
|
** Building SPAs with Django and HTML Over the Wire
|
|
|
|
#+ATTR_HTML: :class center-block image image--cover-book
|
|
[[#/img/books/building-spas.avif][Building SPAs with Django and HTML Over the Wire]]
|
|
|
|
Learn to build real-time single page applications with Python.
|
|
|
|
Buy:
|
|
|
|
- [[https://www.packtpub.com/product/building-spas-with-django-and-html-over-the-wire/9781803240190][Packt]]
|
|
- [[https://www.amazon.com/Real-time-Django-over-Wire-Channels-ebook/dp/B0B3DV54ZT/][Amazon.com]]
|
|
- [[https://www.amazon.es/Real-time-Django-over-Wire-Channels-ebook/dp/B0B3DV54ZT/][Amazon.es]]
|