Executando verificação de segurança...
4

[TUTORIAL] SPA com Django é possível? Sim, e eu vou explicar como!

Django SPA (Single Page Application)

Quando utilizamos uma aplicação SPA, a fluidez que isso traz para o usuário é incrível, um exemplo de projeto utilizando django single page application,é o Instagram, para um iniciante se basear na aplicação do Instagram fica bem complicada, no entanto há uma outra forma de se criar uma aplicação django SPA com uma quantidade de linhas de código até menor e sem perder a interação com o seu usuário, sem a necessidade da utilização dos frameworks baseados em Java.

HTMX

HTMX - Documentation and references

Resumidamente com HTMX, a partir de qualquer parte do seu template, você podera fazer uma requisição e alterar um alvo para o contéudo renderizado por uma view, sem que haja a atualização de toda a página, podendo também utilizar um formulário para enviar requisições, exemplo:

Criando o projeto

$ mkdir Todos
$ cd Todos
$ python3 -m vritualenv env
$ source env/bin/activate
$ pip install django
$ django-admin startproject backend
$ cd backend
$ mkdir apps
$ cd apps
$ ../manage.py startapp todo
$ cd todo
$ mkdir -p static/js/htmx
$ mkdir -p templates/routers/pages

static/js/htmx/htmx.min.js

Crie um arquivo js com nome htmx.min.js copie e cole o código abaixo.
Código JS para HTMX

Estrutura do projeto

.
└── Todos/
    └── backend/
        ├── apps/
        │   └── todo/
        │       ├── templates/
        │       │   ├── base.html
        │       │   └── routers/
        │       │       └── pages/
        │       │           ├── index.html
        │       │           ├── about.html
        │       │           └── contact.html
        │       ├── static/
        │       │   └── js/
        │       │       └── htmx/
        │       │           └── htmx.min.js
        │       ├── urls.py
        │       ├── apps.py
        │       └── views.py
        ├── backend/
        │   ├── settings.py
        │   └── urls.py
        └── manage.py

backend/settings.py

import os

INSTALLED_APPS = [
...
'apps.todo',
]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR,'templates')],
         ...
    },
]

LANGUAGE_CODE = 'pt-br'
TIME_ZONE = 'America/Sao_Paulo'

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR,'static')

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR,'media')

backend/urls.py

from django.contrib import admin
from django.urls import path,include
from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),

    #APPS
    path('',include('apps.todo.urls')),

] + static(settings.STATIC_URL , document_root=settings.STATIC_ROOT)

urlpatterns += static(settings.MEDIA_URL , document_root=settings.MEDIA_ROOT)
    

todo/apps.py

from django.apps import AppConfig


class CoreConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'apps.todo'

todo/urls.py

from django.urls import path
from . import views
urlpatterns =[
    path('',views.base, name="base"),
    path('index/',views.index, name="index"),
    path('about/',views.about, name="about"),
    path('contact/',views.contact, name="contact"),
]
    

todo/template/base.hml

ANTEÇÃO, no atributo hx-get="" você poderá escolher uma das duas opções, utilizar metodo URL built-ins do django {% url '' %} ou utilizar o caminho escrito urls.py, qual seria esse caminho?

    path('index/', views.index,name="index")
            ^-CAMINHO                  ^-URL UTILIZADA NO BUILT-INS

{% load static%}
<!DOCTYPE html>
<html lang="pt-br">
    <head>
        <title>Todo</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        
         <!-- HTMX -->
        <script src="{% static 'js/htmx/htmx.min.js' %}"></script>
    </head>

    <body>
        <nav>
            <li 
                hx-get="{% url 'index'%} ou /index/"
                hx-trigger="click"
                hx-target="#MainPage"
                hx-swap="outterHTML"
            >
                Index
            </li>
            
             <li 
                hx-get="{% url 'about'%} ou /about/"
                hx-trigger="click"
                hx-target="#MainPage"
                hx-swap="outterHTML"
            >
                About
            </li>
            
             <li 
                hx-get="{% url 'contact'%} ou /contact/"
                hx-trigger="click"
                hx-target="#MainPage"
                hx-swap="outterHTML"
            >
                Contact
            </li>
        </nav>

        <main id="MainPage">
            #RENDERIZAÇÃO OCORRERÁ AQUI
        </main>
        <!-- CSRF_TOKEN -->
        <script>
            document.body.addEventListener('htmx:configRequest', (event) =>
                event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}'
            );
        </script>
    </body>
</html>

todo/views.py

from django.shortcuts import render

def base(request):
    """
    BASE PARA TEMPLATES
    """
    return render(request, 'base.html')

def index(request):
    """
    Não necessita de uma verificação do tipo de requisição,
    pois retornaremos apenas um layout
    """
    return render(request,"routers/pages/index.html")
    
    
def about(request):
    """
    Não necessita de uma verificação do tipo de requisição,
    pois retornaremos apenas um layout
    """
    return render(request,"routers/pages/about.html")
    
def contact(request):
    """
    Não necessita de uma verificação do tipo de requisição,
    pois retornaremos apenas um layout,neste caso para enviar
    um formulário, deve-se criar outra view com um template próprio
    para seu formulário.
    """
    return render(request,"routers/pages/contact.html")

todo/templates/routers/pages/index.html

<div>
    <h1> Index </h1>
</div>

todo/templates/routers/pages/about.html

<div>
    <h1> About </h1>
</div>

todo/templates/routers/pages/contact.html

<div>
    <h1> Contact </h1>
</div>

Em Todos/backend, utilizando o terminal, com o seu Virtual Enviroment ativo:

python manage.py migrate
python manage.py runserver

Está pronto, você tem uma página SPA.


Tudo bem pessoal?
Sou apenas um entusiasta da programação, e escolhi o python como minha linguagem de programação. Estou no começo de um grande caminho.
Minha história é complicada, estudo farmácia 5/10, e tenho vontade de migrar para a área da programação, ainda não consegui minha primeira vaga como DEV, mas estou correndo atrás.
Espero que gostem e aceito críticas construtivas, e dicas também, sempre com intuito de melhorar.

Eclesiastes 3

https://github.com/Ilopesr
https://www.linkedin.com/in/igorlopr/

Carregando publicação patrocinada...
1

o HTMLX parece muito o Unpoly - você conhece? Basta algumas TAGS e o site MPA parece um SPA https://unpoly.com/

Se uma olhada de não conhece!

Me parece não sei que HTMLX é um pouco mais complicado que o Unpoly mas nunca mexi
com HTMLX pode ser que eu esteja totalmente errado!

Mas é legal para um PWA ou um webview ter um MPA que parece SPA.

Eu mesmo estou planejanto um projeto com unpoly para criar um PWA

1

Cara interessante não conhecia o Unlopy, tava dando uma olhada e achei bem dificil, mas é por que eu ainda não tentei né, mas o HTMX ele é bem simplificado, você precisa de poucos atributos para alcançar o que você precisa, como fazer um modal

pages/documentacao/entrada.html

<li 
    class="font-bold flex gap-2 justify-center items-center cursor-pointer rounded-md px-2 bg-teal-400 hover:bg-teal-600 border-2 border-teal-400 border-opacity-20 text-white"
    hx-get="pages/documentacao/modals/entry.html"
    hx-trigger="click"
    hx-target="#modal"
    hx-swap="outterHTML"
>
    <i class="fa-solid fa-plus"></i>
    <span>Adicionar</span>
</li>


<!-- MODAL -->

<div id="modal">


</div>

pages/documentacao/modals/entry.html

<div data-entry-modal="entry" class="fixed top-0 left-0 bottom-0 right-0">
  <div class="flex flex-row justify-center items-center h-full w-full uppercase font-thin">
    <form class="flex flex-col gap-4 bg-teal-800 p-4 relative rounded-md">
      <span data-entry-close="close" class="absolute top-0 right-0 p-2 text-white hover:text-teal-400">
        <i class="fa-solid fa-close"></i>
      </span>
      <div class="pt-5">
        <label class="flex flex-col w-full" for="typification">
          <p class="text-white">Tipificação</p>

          <select class="text-thin" name="typification" id="typification">
            <option value="fq">Liberação</option>
            <option value="fq">Validação</option>
            <option value="fq">Validação-liberação</option>
          </select>
        </label>
        <label class="flex flex-col w-full" for="code">
          <p class="text-white">Código</p>

          <input type="text" name="code" id="code">
        </label>
        <label class="flex flex-col w-full" for="name">
          <p class="text-white">Nome</p>

          <input type="text" name="name" id="name">
        </label>

        <label class="flex flex-col w-full" for="entry_at">
          <p class="text-white">Data da entrada</p>

          <input type="date" name="entry_at" id="entry_at">
        </label>
        <label class="flex flex-col w-full" for="type">
          <p class="text-white">Tipo</p>

          <input type="text" name="type" id="type">
        </label>

        <label class="flex flex-col w-full" for="lab">
          <p class="text-white">Laboratório</p>
          <select class="text-thin" name="lab" id="lab">
            <option value="fq">Físiquo-químico</option>
            <option value="fq">Microbiólogico</option>
          </select>
        </label>
      </div>
      <input type="submit" value="Enviar" class="bg-teal-400 hover:bg-teal-600">

    </form>
  </div>
  <script>
    close_modal("data-entry-modal", "data-entry-close")
  </script>
</div>

main.js

function close_modal(modal, close) {
  const closeButton = document.querySelector(`[${close}]`)
  const modalSelect = document.querySelector(`[${modal}]`)

  closeButton.addEventListener('click', (e) => {
    e.stopPropagation();
    if (!modalSelect.classList.contains("hidden")) {
      modalSelect.remove()
    }
  })
}

Exemplo

0
1

Alguma dica de como melhorar? Tornei irrelevante o comentário pois, não houve nenhuma critica construtiva com formas de aprimoramento.

1

Pode-se argumentar que colocar o significado da sigla ajuda, mas isso tira o sentido de usar a sigla, então é um argumento ruim.
Comentei pq achei curiosa a minha demora em entender o que de fato estava escrito.

1