Diario de Python | #15. Combinando Django y React
Maximiliano Burgos
Posted on April 28, 2023
Como comenté en artículos anteriores, una de mis metas con Python era alcanzar algún tipo de camino pavimentado en el rol de Full Stack Dev.
Para esto, me centré en la idea de que podía desarrollar web desde el backend (eligiendo Django en el proceso), pero todavía no tenía claro como iba a encarar el frontend.
Hoy en día, cuando grito "frontend" en la cima de la montaña, el eco me devuelve "React" o "Angular". Decidí decantarme por el primero (gran plot twist, está en el título del artículo); y comencé a aprender desde cero.
Ya sabía Javascript
Tengo que aclarar este detalle porque mucha gente que lee mis artículos se está metiendo ahora en el mundo de sistemas. Tengan en cuenta que llevo 14 años en el asunto, lo cual me ha dado suficiente tiempo para recorrer algunos lenguajes populares de la época.
Javascript y Typescript son los lenguajes de programación por excelencia para utilizar React, siendo el segundo (creo) el más recomendado.
Esto me permitió reducir significativamente la curva de aprendizaje en React. Si han programado JS, les comento que es muy importante saber acerca de asincronía y promesas para dominar esta librería.
React es una librería, no un framework
Van a escuchar en muchos cursos esta frase, pero pocos se toman el tiempo adecuado para explicarlo: a diferencia de Angular (un framework complejo), React es una librería que se acopla a nuestro proyecto.
Esto implica que la estructura del mismo es nuestra responsabilidad: en un framework ya tenemos lineamientos y reglas para evitar las malas prácticas. Pero una librería no es más que un conjunto de funcionalidades que nos van a facilitar la vida, siempre y cuando seamos nosotros los responsables de incluirla en el ecosistema de nuestros proyectos.
React te va a dar la capacidad de reutilizar componentes, acompañado de un ciclo de vida y una arquitectura propia de capas. Esto es genial, y te proporciona una libertad que debes controlar en tus proyectos para sacarle el máximo potencial.
Manos a la obra
Siguiendo un tutorial de Fazt, armé un todolist que utiliza tanto React como Django (y DRF).
En primer lugar armé un proyecto que incluyera mi app "task", la cual incluye el siguiente modelo:
from django.db import models
class Task(models.Model):
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
done = models.BooleanField(default=False)
def __str__(self) -> str:
return self.title
Luego escribí el serializador para este modelo:
from rest_framework import serializers
from .models import Task
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = '__all__'
Ahora en views.py, armé la vista:
from rest_framework import viewsets
from .serializers import TaskSerializer
from .models import Task
class TaskView(viewsets.ModelViewSet):
serializer_class = TaskSerializer
queryset = Task.objects.all()
ModelViewSet me provee de todos los métodos de un CRUD típico: GET, POST, PUT, PATCH, DELETE. Ahora solo me quedaba mapear la url:
from django.urls import path, include
from .views import TaskView
from rest_framework import routers
from rest_framework.documentation import include_docs_urls
router = routers.DefaultRouter()
router.register(f'tasks', TaskView, 'tasks')
urlpatterns = [
path('api/v1/', include(router.urls)),
path('docs/', include_docs_urls(title="Tasks API")),
]
Por un lado, armé el objeto router que me ayudaría a mapear los métodos que dispone mi ViewSet. Luego también incluí include_docs_urls, el cual me genera una página estilo documentación de APIs muy funcional.
También tuve que agregar una dirección para CORS, la cual sería el origen de mi app en React:
CORS_ALLOWED_ORIGINS = [
'http://127.0.0.1:5173'
]
It's React time!
Con la API funcionando, solo quedaba armar el frontend. No existe una combinación como tal entre React y Django, pero si pude armar una carpeta client dentro del proyecto, que si bien esta separada, cumple su función de interactuar con la API.
En este momento había tomado un par de atajos distintos a la implementación de Fazt, por lo cual decidí primero armar el archivo de APIs completo:
import axios from 'axios'
const taskApi = axios.create({
baseURL: 'http://localhost:8000/tasks/api/v1/tasks/'
})
export const getAllTasks = () => taskApi.get('/')
export const getTask = (id) => taskApi.get(`/${id}/`)
export const createTask = (task) => taskApi.post('/', task)
export const deleteTask = (id) => taskApi.delete(`/${id}`)
export const updateTask = (id, task) => taskApi.put(`/${id}/`, task)
Utilizando axios, armé los métodos que interactúan con la API de Django. Confieso que no me termina de convencer la URL final que queda, algo que probablemente en el futuro cambie.
Ruteo
En principio definí un BrowserRouter, el cual lleva a distintas pages que utilizan determinados componentes:
function App() {
return (
<BrowserRouter>
<div className="container mx-auto">
<Navigation />
<Routes>
<Route path="/" element={<Navigate to="/tasks" />} />
<Route path="/tasks" element={<TasksPage />} />
<Route path="/tasks-create" element={<TaskFormPage />} />
<Route path="/tasks/:id" element={<TaskFormPage />} />
</Routes>
</div>
<Toaster />
</BrowserRouter>
);
}
Van a ver un par de clases css raras, eso es porque estoy utilizando Tailwind para los estilos.
En el orden actual, las rutas son:
- /: redirecciona a /tasks.
- /tasks: muestra la lista de tareas existentes
- /tasks-create: un formulario para crear nuevas tareas
- /tasks/🆔 un formulario que trae una tarea cargada.
Esta última ruta se utiliza tanto para editar una tarea como también eliminarla.
El flujo de mostrar tareas
Para no generar un artículo kilométrico, les voy a mostrar una funcionalidad que demuestra la interacción entre React y Django.
Sabemos que para mostrar la página que visualiza todas las tareas, utilizamos el componente TaskPage:
<Route path="/tasks" element={<TasksPage />} />
Este componente retorna TaskList:
export function TaskList() {
const [tasks, setTasks] = useState([])
useEffect(() => {
async function loadTasks() {
const res = await getAllTasks()
setTasks(res.data)
}
loadTasks()
}, [])
return <div className="grid grid-cols-3 gap-3">
{tasks.map(task => (
<TaskCard key={task.id} task={task} />
))}
</div>
}
Mediante useEffect, el cual se va a ejecutar apenas se cree el componente, llamamos a getAllTasks() (API de Django) asincrónicamente, y luego mostramos cada tarea mediante tasks.map, el cual renderizará dicha información en TaskCard.
De esta manera logramos una comunicación limpia y fluida, dado que nuestra API funciona como cualquier otra y React llama sus métodos dependiendo del componente que los requiera.
Conclusiones
He aquí, señoras y señores (procedo a levantarme de la silla), la sagrada unión de los dos mundos (levanto los brazos, mirando a la audiencia).
Espero que hayan disfrutado de leer este artículo tanto como yo al escribirlo, ¡y nos vemos en los próximos!
Posted on April 28, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.