mirror of
https://github.com/Django-LiveView/docs.git
synced 2024-12-23 03:15:36 +01:00
Add tutorials
This commit is contained in:
parent
e11a032cc3
commit
889a5e2767
@ -90,6 +90,12 @@ a:hover,
|
|||||||
grid-gap: 1rem;
|
grid-gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (width < 600px) {
|
||||||
|
.nav-main__list > li:first-child {
|
||||||
|
margin-right: 6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.nav-main__logo {
|
.nav-main__logo {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
margin-bottom: calc(var(--gap-m) * -1);
|
margin-bottom: calc(var(--gap-m) * -1);
|
||||||
@ -101,12 +107,6 @@ a:hover,
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width < 600px) {
|
|
||||||
.nav-main__link--logo {
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.center-block {
|
.center-block {
|
||||||
display: block;
|
display: block;
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
@ -152,9 +152,11 @@ a:hover,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button:hover {
|
.button:hover {
|
||||||
|
--translate: 3px;
|
||||||
border-color: var(--color-brown);
|
border-color: var(--color-brown);
|
||||||
box-shadow: 0 5px 0 var(--color-brown);
|
box-shadow: var(--translate) var(--translate) 0 var(--color-brown);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
transform: translate(calc(-1 * var(--translate)), calc(-1 * var(--translate)));
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
@ -178,7 +180,7 @@ a:hover,
|
|||||||
|
|
||||||
.header .nav__list {
|
.header .nav__list {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr repeat(4, auto);
|
grid-template-columns: 1fr repeat(5, auto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width < 600px) {
|
@media (width < 600px) {
|
||||||
@ -288,6 +290,11 @@ a:hover,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-docs__list {
|
||||||
|
list-style: "➔ ";
|
||||||
|
list-style-position: inside;
|
||||||
|
}
|
||||||
|
|
||||||
/* Home */
|
/* Home */
|
||||||
|
|
||||||
.nav-home__list {
|
.nav-home__list {
|
||||||
@ -333,7 +340,7 @@ a:hover,
|
|||||||
/* Docs */
|
/* Docs */
|
||||||
.docs {
|
.docs {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 8rem auto;
|
grid-template-columns: 10rem auto;
|
||||||
gap: var(--gap-l);
|
gap: var(--gap-l);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,6 +355,12 @@ a:hover,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tutorials */
|
||||||
|
.tutorials ul {
|
||||||
|
list-style: "📚 ";
|
||||||
|
list-position: inside;
|
||||||
|
}
|
||||||
|
|
||||||
/* Footer */
|
/* Footer */
|
||||||
.footer {
|
.footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
669
one.org
669
one.org
@ -56,35 +56,23 @@ The same process is repeated for each action, such as clicking a button, submitt
|
|||||||
- Everything is asynchronous by default.
|
- Everything is asynchronous by default.
|
||||||
- Don't learn anything new. If you know Python, you know how to use Django LiveView.
|
- Don't learn anything new. If you know Python, you know how to use Django LiveView.
|
||||||
|
|
||||||
Are you ready to create your first Realtime SPA? Let's go to the [[#/docs/quickstart/][Quickstart]].
|
Are you ready to create your first Realtime SPA? Let's go to the [[#/tutorials/quickstart/][Quickstart]].
|
||||||
|
|
||||||
* Quickstart
|
* Install
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ONE: one-custom-default-doc
|
:ONE: one-custom-default-doc
|
||||||
:CUSTOM_ID: /docs/quickstart/
|
:CUSTOM_ID: /docs/install/
|
||||||
:TITLE: Quickstart
|
:TITLE: Install
|
||||||
:DESCRIPTION: Get started with Django LiveView the easy way.
|
:DESCRIPTION: Install Django LiveView.
|
||||||
:END:
|
:END:
|
||||||
|
|
||||||
Welcome to the Quickstart guide. Here you will learn how to create your first Realtime SPA using Django LiveView. I assume you have a basic understanding of Django and Python.
|
You can install Django LiveView with ~pip~.
|
||||||
|
|
||||||
All the steps are applied in a [[https://github.com/Django-LiveView/minimal-template][minimalist template]].
|
|
||||||
|
|
||||||
** 1. Install Django
|
|
||||||
|
|
||||||
Install Django, create a project and an app.
|
|
||||||
|
|
||||||
** 2. Install LiveView
|
|
||||||
|
|
||||||
Install django-liveview with ~pip~.
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_SRC sh
|
||||||
pip install django-liveview
|
pip install django-liveview
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** 3. Modify the configuration
|
Then add ~liveview~ to your installed ~INSTALLED_APPS~ in your ~settings.py~.
|
||||||
|
|
||||||
Add ~liveview~ to your installed ~INSTALLED_APPS~.
|
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
#+BEGIN_SRC python
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
@ -94,264 +82,21 @@ INSTALLED_APPS = [
|
|||||||
]
|
]
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Then indicate in which previously created App you want to implement LiveView.
|
You need to previously have installed ~channels~ and ~daphne~.
|
||||||
|
|
||||||
|
Now, in ~settings.py~, indicate in which previously created App you want to integrate LiveView.
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
#+BEGIN_SRC python
|
||||||
LIVEVIEW_APPS = ["website"]
|
LIVEVIEW_APPS = ["website"]
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** 4. Migration
|
Finally, execute the migrations so that the LiveView tables are generated.
|
||||||
|
|
||||||
Execute the migrations so that the LiveView tables are generated.
|
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
#+BEGIN_SRC python
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** 5. ASGI
|
We strongly recommend that you follow the [[#/tutorials/quickstart/][Quickstart]] to see the installation in action.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
|
||||||
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())])
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
** 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~.
|
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
|
||||||
# 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)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
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~.
|
|
||||||
|
|
||||||
#+BEGIN_SRC 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>
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
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 ~pages~ in your template folder and inside it create a file called ~home.html~.
|
|
||||||
|
|
||||||
#+BEGIN_SRC 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>
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
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 [[https://github.com/Django-LiveView/assets/archive/refs/heads/main.zip][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:
|
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
|
||||||
# my-app/views.py
|
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
def home(request):
|
|
||||||
return render(request, "pages/home.html")
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
With LiveView, on the other hand, you will have the following structure.
|
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
|
||||||
# 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())
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
** 11. Create URL
|
|
||||||
|
|
||||||
Finally, we will create the URL that will render the page.
|
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
|
||||||
# my-app/urls.py
|
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from .views import home
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path("", home, name="home"),
|
|
||||||
]
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
** 12. Run the server
|
|
||||||
|
|
||||||
Run the server.
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
python manage.py runserver
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And open the browser at ~http://localhost:8000/~. You should see the home page with a button that generates a random number.
|
|
||||||
|
|
||||||
#+ATTR_HTML: :class center-block image image--responsive
|
|
||||||
[[#/img/quickstart/minimal-template.webp][Random number]]
|
|
||||||
|
|
||||||
* Actions
|
* Actions
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
@ -689,6 +434,54 @@ You can customize the history management system by editing the ~history~ control
|
|||||||
|
|
||||||
If you want to disable it, remove `startHistory();` from ~assets/js/main.js~.
|
If you want to disable it, remove `startHistory();` from ~assets/js/main.js~.
|
||||||
|
|
||||||
|
* Internationalization
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: one-custom-default-doc
|
||||||
|
:CUSTOM_ID: /docs/internationalization/
|
||||||
|
:TITLE: Internationalization
|
||||||
|
:DESCRIPTION: Internationalization of Django LiveView.
|
||||||
|
:END:
|
||||||
|
|
||||||
|
Django LiveView uses the same internationalization system as Django. You can read more about it in the [[https://docs.djangoproject.com/en/dev/topics/i18n/][Django documentation]]. However, let's go deeper.
|
||||||
|
|
||||||
|
Every time that the client sends a request for action, it sends the language that the user has set in the browser.
|
||||||
|
|
||||||
|
#+BEGIN_SRC json
|
||||||
|
{
|
||||||
|
"action": "blog_list->send_page",
|
||||||
|
"data": {
|
||||||
|
"lang": "es"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
The ~lang~ attribute is extracted directly from the ~<html>~ tag.
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
{% load static i18n %}
|
||||||
|
<!doctype html>{% get_current_language as CURRENT_LANGUAGE %}
|
||||||
|
<html lang="{{ CURRENT_LANGUAGE }}">
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
You can access the language with the ~lang~ parameter of the action using the ~@enable_lang~ decorator.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python
|
||||||
|
@enable_lang
|
||||||
|
@loading
|
||||||
|
async def send_page(consumer, client_data, lang=None):
|
||||||
|
print(lang)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Or you could read it with the ~lang~ key of the ~client_data~ parameter.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python
|
||||||
|
@loading
|
||||||
|
async def send_page(consumer, client_data):
|
||||||
|
print(client_data["data"]["lang"])
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
You can read the tutorial [[#/tutorials/internationalize-with-subdomains/][Internationalize with subdomains]] to see how to create a multilingual website with subdomains.
|
||||||
|
|
||||||
* Deploy
|
* Deploy
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ONE: one-custom-default-doc
|
:ONE: one-custom-default-doc
|
||||||
@ -767,6 +560,348 @@ Yes, you can.
|
|||||||
|
|
||||||
Only me and my free time.
|
Only me and my free time.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* Tutorials
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: one-custom-default-tutorials
|
||||||
|
:CUSTOM_ID: /tutorials/
|
||||||
|
:TITLE: Tutorials
|
||||||
|
:DESCRIPTION: Tutorials about Django LiveView.
|
||||||
|
:END:
|
||||||
|
|
||||||
|
** Tutorials
|
||||||
|
|
||||||
|
Learn how to create a Django LiveView application step by step.
|
||||||
|
|
||||||
|
- [[#/tutorials/quickstart/][Quickstart]]
|
||||||
|
- [[#/tutorials/internationalize-with-subdomains/][Internationalize with subdomains]]
|
||||||
|
|
||||||
|
* Quickstart
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: one-custom-default-page
|
||||||
|
:CUSTOM_ID: /tutorials/quickstart/
|
||||||
|
:TITLE: Quickstart
|
||||||
|
:DESCRIPTION: Get started with Django LiveView the easy way.
|
||||||
|
:END:
|
||||||
|
|
||||||
|
Welcome to the Quickstart guide. Here you will learn how to create your first Realtime 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]].
|
||||||
|
|
||||||
|
** 1. Install Django
|
||||||
|
|
||||||
|
Install Django, create a project and an app.
|
||||||
|
|
||||||
|
** 2. Install LiveView
|
||||||
|
|
||||||
|
Install django-liveview with ~pip~.
|
||||||
|
|
||||||
|
#+BEGIN_SRC sh
|
||||||
|
pip install django-liveview
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
** 3. Modify the configuration
|
||||||
|
|
||||||
|
Add ~liveview~ to your installed ~INSTALLED_APPS~.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"daphne",
|
||||||
|
"channels",
|
||||||
|
"liveview",
|
||||||
|
]
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Then indicate in which previously created App you want to implement LiveView.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python
|
||||||
|
LIVEVIEW_APPS = ["website"]
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
** 4. Migration
|
||||||
|
|
||||||
|
Execute the migrations so that the LiveView tables are generated.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python
|
||||||
|
python manage.py migrate
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
** 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.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python
|
||||||
|
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())])
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
** 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~.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python
|
||||||
|
# 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)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
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~.
|
||||||
|
|
||||||
|
#+BEGIN_SRC 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>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
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 ~pages~ in your template folder and inside it create a file called ~home.html~.
|
||||||
|
|
||||||
|
#+BEGIN_SRC 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>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
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 [[https://github.com/Django-LiveView/assets/archive/refs/heads/main.zip][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:
|
||||||
|
|
||||||
|
#+BEGIN_SRC python
|
||||||
|
# my-app/views.py
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
def home(request):
|
||||||
|
return render(request, "pages/home.html")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
With LiveView, on the other hand, you will have the following structure.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python
|
||||||
|
# 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())
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
** 11. Create URL
|
||||||
|
|
||||||
|
Finally, we will create the URL that will render the page.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python
|
||||||
|
# my-app/urls.py
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from .views import home
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", home, name="home"),
|
||||||
|
]
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
** 12. Run the server
|
||||||
|
|
||||||
|
Run the server.
|
||||||
|
|
||||||
|
#+BEGIN_SRC sh
|
||||||
|
python manage.py runserver
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
And open the browser at ~http://localhost:8000/~. You should see the home page with a button that generates a random number when clicked.
|
||||||
|
|
||||||
|
#+ATTR_HTML: :class center-block image image--responsive
|
||||||
|
[[#/img/quickstart/minimal-template.webp][Random number]]
|
||||||
|
|
||||||
|
Congratulations! You have created your first Realtime SPA using Django LiveView.
|
||||||
|
|
||||||
|
The next step is to create a more complex application. You can read other [[#/tutorials/][tutorials]] or go to the [[#/docs/install/][documentation]].
|
||||||
|
|
||||||
|
* Internationalize with subdomains
|
||||||
|
:PROPERTIES:
|
||||||
|
:ONE: one-custom-default-page
|
||||||
|
:CUSTOM_ID: /tutorials/internationalize-with-subdomains/
|
||||||
|
:TITLE: Internationalize with subdomains
|
||||||
|
:DESCRIPTION: Create a multilingual website with subdomains.
|
||||||
|
:END:
|
||||||
|
|
||||||
|
Here you will learn how to create a multilingual website using Django LiveView. We recommend using subdomains to define the language (~en.example.com~, ~es.example.com~...), instead of using prefixes in addresses (~example.com/en/blog/~, ~example.com/es/blog/~). They simplify SEO, maintain consistency in the Sitemap and are easy to test.
|
||||||
|
|
||||||
|
** 1. Configure the subdomains
|
||||||
|
|
||||||
|
** 2. Create the subdomains
|
||||||
|
|
||||||
|
** 3. Configure the languages
|
||||||
|
|
||||||
|
** 4. Redirection with Middleware
|
||||||
|
|
||||||
|
** 5. First text
|
||||||
|
|
||||||
|
** 6. Make messages
|
||||||
|
|
||||||
|
** 7. Compile messages
|
||||||
|
|
||||||
|
** 8. Selector of languages
|
||||||
|
|
||||||
* Source code
|
* Source code
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ONE: one-custom-default-page
|
:ONE: one-custom-default-page
|
||||||
|
28
onerc.el
28
onerc.el
@ -97,13 +97,15 @@
|
|||||||
(:li.nav-main__item
|
(:li.nav-main__item
|
||||||
(:a.nav-main__link.nav-main__link--logo (@ :href "/") (:img.nav-main__logo (@ :alt "Django LiveView" :src "/img/logo.webp"))))
|
(:a.nav-main__link.nav-main__link--logo (@ :href "/") (:img.nav-main__logo (@ :alt "Django LiveView" :src "/img/logo.webp"))))
|
||||||
(:li.nav-main__item
|
(:li.nav-main__item
|
||||||
(:a.button.nav-main__link (@ :href "/docs/quickstart/") "Docs"))
|
(:a.button.nav-main__link (@ :href "/docs/install/") "Docs"))
|
||||||
(:li.nav-main__item
|
(:li.nav-main__item
|
||||||
(:a.button.nav-main__link (@ :href "/source-code/") "Source code"))
|
(:a.button.nav-main__link (@ :href "/tutorials/") "Tutorials"))
|
||||||
(:li.nav-main__item
|
(:li.nav-main__item
|
||||||
(:a.button.nav-main__link (@ :href "https://django-liveview-demo.andros.dev/" :target "_blank") "Demo"))
|
(:a.button.nav-main__link (@ :href "https://django-liveview-demo.andros.dev/" :target "_blank") "Demo"))
|
||||||
(:li.nav-main__item
|
(:li.nav-main__item
|
||||||
(:a.button.nav-main__link (@ :href "/books/") "Books"))))))
|
(:a.button.nav-main__link (@ :href "/books/") "Books"))
|
||||||
|
(:li.nav-main__item
|
||||||
|
(:a.button.nav-main__link (@ :href "/source-code/") "Source code"))))))
|
||||||
,tree-content
|
,tree-content
|
||||||
(:footer.footer
|
(:footer.footer
|
||||||
(:p "Created with " (:i (@ :aria-label "love") "❤️") " by " (:a.link (@ :href "https://andros.dev/" :target "_blank") "Andros Fenollosa") " with " (:a.link (@ :href "https://one.tonyaldon.com/" :target "_blank") "one.el"))
|
(:p "Created with " (:i (@ :aria-label "love") "❤️") " by " (:a.link (@ :href "https://andros.dev/" :target "_blank") "Andros Fenollosa") " with " (:a.link (@ :href "https://one.tonyaldon.com/" :target "_blank") "one.el"))
|
||||||
@ -168,7 +170,7 @@
|
|||||||
(:nav.nav-docs
|
(:nav.nav-docs
|
||||||
(:ul.nav__list.nav__list--docs.nav-docs__list
|
(:ul.nav__list.nav__list--docs.nav-docs__list
|
||||||
(:li.nav-docs__item
|
(:li.nav-docs__item
|
||||||
(:a.nav-docs__link (@ :href "/docs/quickstart/") "Quickstart"))
|
(:a.nav-docs__link (@ :href "/docs/install/") "Install"))
|
||||||
(:li.nav-docs__item
|
(:li.nav-docs__item
|
||||||
(:a.nav-docs__link (@ :href "/docs/actions/") "Actions"))
|
(:a.nav-docs__link (@ :href "/docs/actions/") "Actions"))
|
||||||
(:li.nav-docs__item
|
(:li.nav-docs__item
|
||||||
@ -177,6 +179,8 @@
|
|||||||
(:a.nav-docs__link (@ :href "/docs/routing/") "Routing"))
|
(:a.nav-docs__link (@ :href "/docs/routing/") "Routing"))
|
||||||
(:li.nav-docs__item
|
(:li.nav-docs__item
|
||||||
(:a.nav-docs__link (@ :href "/docs/history/") "History"))
|
(:a.nav-docs__link (@ :href "/docs/history/") "History"))
|
||||||
|
(:li.nav-docs__item
|
||||||
|
(:a.nav-docs__link (@ :href "/docs/internationalization/") "Internationalization"))
|
||||||
(:li.nav-docs__item
|
(:li.nav-docs__item
|
||||||
(:a.nav-docs__link (@ :href "/docs/deploy/") "Deploy"))
|
(:a.nav-docs__link (@ :href "/docs/deploy/") "Deploy"))
|
||||||
(:li.nav-docs__item
|
(:li.nav-docs__item
|
||||||
@ -185,6 +189,22 @@
|
|||||||
(:main.main.main--docs
|
(:main.main.main--docs
|
||||||
,content))))))
|
,content))))))
|
||||||
|
|
||||||
|
(defun one-custom-default-tutorials (page-tree pages _global)
|
||||||
|
"Default render function by tutorials page."
|
||||||
|
(let* ((title (org-element-property :raw-value page-tree))
|
||||||
|
(description (org-element-property :DESCRIPTION page-tree))
|
||||||
|
(path (org-element-property :CUSTOM_ID page-tree))
|
||||||
|
(content (org-export-data-with-backend
|
||||||
|
(org-element-contents page-tree)
|
||||||
|
'one-ox nil))
|
||||||
|
(website-name (one-default-website-name pages))
|
||||||
|
(nav (one-default-nav path pages)))
|
||||||
|
(render-layout-html
|
||||||
|
title
|
||||||
|
description
|
||||||
|
(jack-html `(:main.main
|
||||||
|
(:section.tutorials
|
||||||
|
(:div.container ,content)))))))
|
||||||
;; Sitemap
|
;; Sitemap
|
||||||
|
|
||||||
(defun make-sitemap (pages tree global)
|
(defun make-sitemap (pages tree global)
|
||||||
|
Loading…
Reference in New Issue
Block a user