Bienvenido a la cuarta parte de esta serie para aprender a crear aplicaciones Flask.
En la entrada anterior terminamos de crear nuestro modelo y ha llegado el momento de conectarlo con el controlador y la vista donde mostraremos los elementos usando Jinja2.
Para ello:
- Importaremos el modelo al controlador.
- Desde el controlador llamaremos los métodos de la clase Persona creada en el modelo según el tipo de solicitud o acción realice el cliente.
- Aprenderemos y usaremos la sintaxis de Jinja2 para mostrar variables y ejecutar instrucciones dentro de nuestro template. De esta forma estaremos conectado modelo con controlador y controlador con vista.
En este último post vamos a tratar de terminar con nuestra aplicación y hacer un repaso por los pasos realizados.
Recordemos la arquitectura de nuestro proyecto, en mi caso uso el comando «tree» en Debian:

Como ves, la arquitectura es sencilla: tenemos un modelo (models.py) que gestiona la lógica de negocio y el acceso a la base de datos JSON; un controlador (controlador.py) que maneja las peticiones HTTP; y unas vistas (*.html) que se encargan de la presentación.
Hasta ahora, en las partes anteriores de esta serie, hemos construido el modelo con todas las operaciones CRUD (Crear, Leer, Actualizar, Eliminar) sobre una base de datos JSON. También hemos creado las plantillas HTML básicas. El paso que nos falta es precisamente el más importante: conectar el modelo con el controlador y con las vistas usando Jinja2.
. ├── controlador.py ├── DB │ └── base_de_datos.json ├── models.py ├── templates │ ├── agregar_contacto.html │ ├── editar_contacto.html │ └── index.html └── tree.txt # Estructura que tendra nuestro proyecto
En esta cuarta parte vamos a:
- Importar el modelo (
Persona) en el controlador. - Llamar a los métodos CRUD desde las rutas de Flask según la acción del usuario (GET o POST).
- Pasar los datos del modelo a las vistas usando la función
render_template(). - Utilizar la sintaxis de Jinja2 dentro de los archivos HTML creando plantillas que permitirán listar, modificar, añadir y eliminar personas de nuestra base de datos JSON.
De esta forma, cerraremos el ciclo Modelo → Controlador → Vista, completando así nuestra primera aplicación Flask con un CRUD funcional.

Modelo de desarrollo MVC (Modelo Vista Controlador) y el concepto CRUD para el manejo de bases de datos JSON (No Relacionales) desde el Modelo.
Nuestra aplicación será capaz de procesar las solicitudes desde el navegador e ir modificando la información de la base de datos según sea necesario.
Recordando el código de nuestro proyecto CRUD:
En el post anterior te explique el concepto CRUD y aprendimos a crear un Modelo que haciendo uso de los métodos (Create, Read, Update, Delete) es capaz de trabajar con elementos de una base de datos JSON de forma consistente.
CÓDIGO ANTIGUO
Importar el modelo al controlador
Antes de comenzar a usar o llamar métodos del modelo desde el controlador, debemos importarlo. Para ello lo podemos hacer de las siguientes manera; en el controlador:
from models import Persona
Antes de poder usar los métodos de nuestra clase Persona (la que creamos en models.py), debemos importarla en el controlador. Esto es necesario porque cada archivo en Python es un módulo independiente; si queremos usar algo definido en otro archivo, debemos decirle a Python dónde encontrarlo.
En nuestro caso, el controlador (controlador.py) y el modelo (models.py) están en la misma carpeta, por lo que la importación es directa:
from models import Persona
Con esta línea estamos diciendo: «Del archivo models.py, tráeme la clase Persona para poder usarla aquí».
¿Qué pasa si no importamos? Si intentamos usar Persona sin importarla, Python lanzará un error NameError diciendo que Persona no está definida. Por eso es obligatorio importar.
¿Hay otras formas de importar? Sí, también podrías usar:
import models # Luego usarías models.Persona
Pero la forma from models import Persona es más limpia porque nos permite escribir directamente Persona en lugar de models.Persona cada vez.
Una vez importada, ya podemos llamar a sus métodos (como
leer_contacto,crear_contacto, etc.) desde las rutas de nuestro controlador.
Ahora vamos a recordar el código de nuestro controlador ya que lo primero será trabajar con las solicitudes que nuestro controlador va a aceptar y que método del modelo deberá llamar.
Código de controlador.py (versión inicial):
from flask import Flask, request, render_template #Importamos clase Flask, objeto request de flask (librería)
app = Flask(__name__) #Creamos una app instanciando la clase Flask (automáticamente el nombre de la app)
from models import Persona #IMPORTAMOS EL MODELO
#Rutas - (argumentos: Url) - Función
@app.route('/', methods = ['GET', 'POST']) #Decoramos con método de app que es una instancia de la clase Flask y argumentamos "slash"
def inicio(): #Definimos una función llamada Inicio
if request.method == 'POST':
pass
else:
return render_template('/index.html') #Retornornamos el template usando RENDER_TEMPLATE()
@app.route('/agradecimiento', methods = ['GET']) #Decoramos con método routes la próxima función arguementando la url "/agradecimiento"
def agradecer(): #Definimos una función llamada agradecer
return 'Gracias pythones.net!' #Retorno de la función
#Iniciar app
if __name__ == '__main__': #Condicional de que si la aplicación ejecutada se coincide al nombre de la aplicación
app.run('127.0.0.1', 5000, debug=True) #Método que inicia la app con la dirección, puertos y modo de argumentos
La página de inicio de nuestra app
Recordemos que nuestro controlador carga un template html al inicio de nuestra aplicación. Es momento de comenzar a enlazar la vista con el controlador y este con el modelo.

Bastante cutre y sencilla. ¿Verdad?. Pero bueno lo importante es que funcione, ya luego nos concentraremos en los detalles visuales. Pensando en funcionalidad ahora la página de inicio de nuestra agenda debería mostrar los contactos, las personas que tendremos almacenadas en nuestra base de datos JSON y debería mostrarlas del lado derecho.
Esto sería responder a una solicitud GET, es decir, el cliente estará solicitando obtener datos al momento de cargar nuestro sitio web.

¿Cómo listamos los elementos (Personas) de la base de datos JSON en nuestra plantilla index.html?
Bueno recordarás que en el models.py declaramos el método leer_contacto() que obtenía como argumento el nombre del atributo y el valor para devolvernos una persona.
Pues básicamente lo que podemos hacer es que al momento de una solicitud GET en nuestra página de inicio se llame a este método con el argumento «all» para que devuelva un diccionario de todas las personas que tenemos almacenadas en la base de datos.
Vamos
Se lo pasemos como argumento en una variable a la vista usando la función render_template().
Luego ya desde la vista nos encargamos de manejar y listar los elementos de ese diccionario. Así que a ello:
- Llamar al método del modelo
leer_contacto()desde el controlador respondiendo solicitud GET. - El valor que nos retorne el método del modelo debemos almacenarlo en una variable y enviarlo en la función
render_template()como argumento. - Desde la vista debemos desplegar este elemento usando Jinja2.
Para el primer y segundo paso; recordemos que teníamos un condicional que comprobaba el tipo de solicitud realizada, PERO NO LO VAMOS A NECESITAR, ya que solo aceptaremos solicitudes GET así que solo añadimos la llamada al método leer_contacto() y borraremos lo demás:
from flask import Flask, request, render_template #Importamos clase Flask, objeto request de flask (librería)
app = Flask(__name__) #Creamos una app instanciando la clase Flask (automáticamente el nombre de la app)
from models import Persona #Recuerda importar la clase desde el modelo
#Rutas - (argumentos: Url) - Función
@app.route('/', methods = ['GET']) #Decoramos con método de app que es una instancia de la clase Flask y argumentamos "slash"
def inicio(): #Definimos una función llamada Inicio
#Llamamos al método para listar personas y lo almacenamos en "Todos"
Todos = Persona.leer_contacto('id', 'all')
return render_template('/index.html', Todos = Todos) #Retornornamos el template + Todos
@app.route('/agradecimiento', methods = ['GET']) #Decoramos con método routes la próxima función arguementando la url "/agradecimiento"
def agradecer(): #Definimos una función llamada agradecer
return 'Gracias pythones.net!' #Retorno de la función
#Iniciar app
if __name__ == '__main__': #Condicional de que si la aplicación ejecutada se coincide al nombre de la aplicación
app.run('127.0.0.1', 5000, debug=True) #Método que inicia la app con la dirección, puertos y modo de argumentos
Aquí estamos llamando el template index.html como habitualmente lo hacemos pero también pasando el argumento Todos que nos devuelve el modelo. Tal como se indica debemos pasar el nombre y el valor. El cual contiene un diccionario con todos las personas de la base de datos.
Ahora debemos listar este contenido en la vista, en el apartado dentro de index.html que creamos para ello.
Pero antes debo enseñarte estos conceptos que son los que aplicaremos aquí, donde Jinja2 será el motor de las vistas para nuestras aplicaciones y por ende debes comprenderlo a fondo:

Motor de plantillas Jinja2 – Mostrar código Python en los archivos HTML
Anteriormente en otro post redacte acerca del motor Jinja2 que es la librería que utiliza FLASK para renderizar las plantillas y nos permite incluir resultado de código Python dentro del HTML siguiendo ciertas «normas de escritura o reglas de interpretación». Las cuales trataré de resumir de forma breve pero puedes encontrar más información leyendo la Documentación oficial de dicha librería; una práctica a la que deberías habituarte.
Variables en Jinja2 – ¿Cómo creamos y mostramos una variable?
Aclaremos primero:
- Toda variable a mostrar debe ser pasada como argumento en la función render_template siguiendo la siguiente estructura:
render_template('archivo_html', var = valor)dondevarcorresponde a la variable yvalora su asignación. - Si la variable no es pasada como argumento entonces deberá ser creada en el template mismo usando el motor Jinja2 mediante
set. - No podemos incluir variables si no son creadas o importadas en la renderización del template.
Dicho esto:
Mostrar variables
Para mostrar una variable usamos {{ var }} donde var es el nombre de la variable pasada como argumento o creada antes.
Crear una o múltiples variables
Para crear variables y asignarles algún valor dentro del mismo template simplemente usamos set así:
{% set var = "texto" %} #Creamos una variable llamada "var" con el contenido "texto"
{% set var1, var2 = "texto1", "texto2" %} #Creamos dos variables "var1" y "var2" con el respectivo contenido: en var1 = "texto1" y en var2 = "texto2"
Ejecutar instrucciones de código Python – Funciones, bucle for y condicionales
Para el caso de las instrucciones siempre utilizamos {% para abrir cada elemento de la instrucción y %} para cerrar. Excepto en el caso de las funciones.
Funciones en jinja2
Las funciones se deben (al igual que las variables) pasar como argumentos o bien crear dentro del template, para pasarlas como argumentos se deben pasar los valores para los parámetros y luego el nombre de la función en render_template() así: return render_template('sumar.html', val1=10, val2=30, funcion=suma) donde suponemos una función suma() sencilla que admite dos parámetros para sumar dos números en el controlador. Y luego en la vista la podemos llamar así:
{{ funcion(val1, val2) }} #Llamamos la función suma que admite 2 parámetros que toman como valor por defecto los que pasamos en render_template()
Si quisiéramos cambiar los valores de val1 y val2 para sumar otros números diferentes a los pasados como argumento en render_template() lo hacemos así:
{% set val1 = 55 %} #Seteamos el valor de "val1" a 55
Y finalmente sumamos nuevamente:
{{ funcion(val1, val2) }}
Condicionales en Jinja2 – IF, ELIF, ELSE
Tanto en el caso de los condicionales como en el de los ciclos o bucles la sintaxis Jinja2 aplica solo al código Python, y aclaro esto porque cuesta acostumbrarse. Pero cada bloque IF o bucle FOR debe abrirse y cerrarse como si de una etiqueta HTML se tratara, por ejemplo:
{% if (1 + 1) == 2 %} #Abrimos un bloque condicional
La suma de 1 + 1 es 2
{% else %}
No se cumple la condición
{% endif %} #Cierre del bloque if
Bucle for
Jinja2 solo nos permite el bucle for, cuya sintaxis es semejante a los condicionales o bloques if, elif, else. Abrimos el bloque y luego lo cerramos. Y se me olvidaba comentar que obviamente puedes colocar HTML dentro de cualquiera de estos bloques así como también incluir otros archivos HTML. Veamos el bucle FOR:
{% for val in [1,2,3,4,5,6,7,8,9] %} #Apertura de bucle for itera lista
{{ val }} #Muestra el contenido
{% endfor %} #Cierre del bucle for
Mostrar elementos dentro de nuestra plantilla index.html
Ahora que hemos aprendido la sintaxis de Jinja2 es momento de aplicarlo. En nuestra plantilla index debemos listar cada persona almacenada en la base de datos pero incluyendo en cada elemento los 3 botones que diseñamos para «Ver, Modificar, Borrar» por ello el bucle for debe incluirlos en cada iteración de la siguiente manera:
Podemos usar un bucle for para iterar el diccionario Todos así:
<div style="width: 20%; float:right; margin-right: 10%; text-align:center;"> <!--Div de la derecha-->
<h2>Personas</h2> <!--Titulo-->
<table border = "1" style = "width: 100%;"><!--Tabla de la derecha-->
{% for (index, persona) in Todos.items() %}
<tr><!--Fila-->
<td>{{persona}}</td><!--Columna que mostrará el nombre y apellido-->
<td><button name = "ver">Ver</button></td><!--Columna que mostrará el botón Ver-->
<td><button name = "editar">Editar</button></td><!--Columna que mostrará el botón Editar-->
<td><button name = "eliminar">Eliminar</button></td><!--Columna que mostrará el botón Eliminar-->
</tr>
{% endfor %}
<tr>
<td><button name = "agregar">Agregar</button></td><!--Columna que mostrará el botón Agregar-->
</tr>
</table>
</div>
Debemos modificar el modelo.py para que incluya el argumento «all» para listar todas las personas en la base de datos en el método READ (Leer datos):
#Método READ -> Leer una persona o contacto de la base de datos
def leer_contacto(atr, valor):
if valor == 'all':
# Devuelve todas las personas con su índice como clave
todas = {}
for i, persona in enumerate(datos["Personas"]):
todas[i] = persona
return todas
else:
encontradas = {}
for persona in datos["Personas"]:
if persona[atr] == valor:
indice = datos["Personas"].index(persona)
encontradas[indice] = persona
return encontradas
Así queda nuestra modelo.py completo:
#BLOQUE IMPORT
import os
import json
#BLOQUE APERTURA ARCHIVO JSON
THIS_FOLDER = os.path.dirname(os.path.abspath(__file__))
my_file = os.path.join(THIS_FOLDER + '/DB/' 'base_de_datos.json')
# --> Función Leer archivo JSON
def leer_json():
with open (my_file, "r") as f:
datos = json.load(f)
f.close()
return datos
datos = leer_json()
# --> Función modificar JSON
def modificar_json():
with open (my_file, "w") as modid:
json.dump(datos, modid, indent=4)
modid.close()
#BLOQUE CLASE PERSONA Y CRUD
class Persona(object):
contador_id = datos["Configuraciones"][0]["contador_id_db"]
def __init__(self, nombre, apellido, apodo, telefono, direccion):
print("El contador está en: ", Persona.contador_id)
self.id = Persona.contador_id #Atriuto id es igual a la variable
self.nombre = nombre
self.apellido = apellido
self.apodo = apodo
self.telefono = telefono
self.direccion = direccion
#Método CREATE -> Crear una nueva persona o contacto de la base de datos
def crear_contacto(self):
datos["Configuraciones"][0]["contador_id_db"]+=1 #Cambiamos el valor de "contador_id_db" (en la base de datos)
modificar_json()
print("El contador está ahora en: ", datos["Configuraciones"][0]["contador_id_db"])
Persona.contador_id +=1 #Sumamos uno a la variable contador_id de la clase
#Creamos nueva instancia
nueva_persona = Persona(
self.nombre,
self.apellido,
self.apodo,
self.telefono,
self.direccion).__dict__ #Guardamos la nueva persona es decir "instancia" dentro de la variable "nueva_persona" como DICCIONARIO
datos['Personas'].append(nueva_persona) #->Entramos a los valores de la clave "Personas" que son Lista y añadimos el nuevo objeto (diccionario)
modificar_json()
#Método READ -> Leer una persona o contacto de la base de datos
#Método READ -> Leer una persona o contacto de la base de datos
def leer_contacto(atr, valor):
if valor == 'all':
# Devuelve todas las personas con su índice como clave
todas = {}
for i, persona in enumerate(datos["Personas"]):
todas[i] = persona
return todas
else:
encontradas = {}
for persona in datos["Personas"]:
if persona[atr] == valor:
indice = datos["Personas"].index(persona)
encontradas[indice] = persona
return encontradas
#Método UPDATE -> Actualizar o modificar un dato de algún contacto en la bd
def actualizar_contacto(id, atr, nuevo_valor):
for persona in datos["Personas"]: #Cada "indice" es un diccionario de persona
if persona["id"] == id: #Si el "id" de ese diccionario es igual al arguemento id
print (persona) #Imprime el diccionario
#Obtenemos el indice de esa persona
indice = datos["Personas"].index(persona) #La variable toma el valor de la posición de la persona
datos["Personas"][indice][atr] = nuevo_valor #Accedemos a ese diccionario> clave y cambiamos su valor
print (datos["Personas"][indice][atr]) #Imprimimos para confirmar el cambio en "datos" (lectura)
modificar_json()
#Método DELETE -> Borrar una persona o contacto de la base de datos
def eliminar_contacto(id):
for persona in datos["Personas"]: #Cada "indice" es un diccionario de persona
if persona["id"] == id: #Si el "id" de ese diccionario es igual al arguemento id
print ("Se va a borrar: ", persona) #Imprime el diccionario
#Obtenemos el indice de esa persona
indice = datos["Personas"].index(persona) #La variable toma el valor de la posición de la persona
datos["Personas"].pop(indice) #Borramos usando el método de listas .pop()
modificar_json()
y también debemos agregar un par de personas a nuestra base de datos para probar nuestro diseño de modelo, entonces nuestro DB/base_de_datos.json queda:
{
"Personas": [
{
"id": 0,
"nombre": "Mariano",
"apellido": "Laca",
"apodo": "Marianito",
"telefono": "11-2345-6789",
"direccion": "Calle Falsa 123, Buenos Aires"
},
{
"id": 1,
"nombre": "Ana",
"apellido": "García",
"apodo": "Anita",
"telefono": "11-9876-5432",
"direccion": "Avenida Siempre Viva 742, CABA"
},
{
"id": 2,
"nombre": "Carlos",
"apellido": "Pérez",
"apodo": "Carlitos",
"telefono": "11-4567-8901",
"direccion": "Calle Libertad 456, La Plata"
},
{
"id": 3,
"nombre": "Laura",
"apellido": "Martínez",
"apodo": "Lau",
"telefono": "11-3456-7890",
"direccion": "Diagonal Norte 789, Rosario"
}
],
"Configuraciones": [
{
"contador_id_db": 4
}
]
}
De esta forma mantenemos la congruencia de contador_id_db indicando que tenemos 4 personas y todas con su id incremental correspondiente; si aún tienes dudas sobre esta consistencia de datos te recomiendo leer el apartado de bases de datos relacional donde explico la importancia del uso de identificadores únicos ID en ambos modelos:
Si todo va bien al recargar nuestro sitio veremos algo así:

Aunque parezca un error, es intencional, ahora sabemos que cada celda almacena un diccionario ya que estamos listando todos los elementos sin aplicar un filtro.
Como ves estamos listando TODAS las personas y se corresponden los 3 botones para cada una de ellas.
El problema es que estéticamente se ve horrible porque además nos está listando todos los datos de cada persona con su par clave valor (diccionario completo), y nosotros solo queríamos mostrar el Nombre en la lista derecha.
Para ello debemos modificar nuestro código especificando solo la clave que queremos mostrar [FILTRAR] así:
<!DOCTYPE html>
<html lang="es"> <!--Cambiamos a español-->
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mi primer App en Flask</title> <!--Modificamos el Título-->
</head>
<body><!--Aquí comienza el cuerpo de nuestro HTML-->
<h1 style = "text-align:center">Directorio Telefónico - Mi primer aplicación en Flask</h1> <!--Titulo principal centrado-->
<br>
<div> <!--Creamos un div contenedor-->
<div style="width: 60%; float:left; text-align:center;"> <!--Creamos el div de la izquierda-->
<h2>Datos completos de la persona</h2> <!--Titulo-->
<table border = "1" style = "width: 100%;"> <!--Creamos la tabla con borde-->
<tr><!--Creamos una fila-->
<td>Nombre</td> <!--Dentro de la fila las columnas-->
<td>Apellido</td>
<td>Apodo</td>
<td>Teléfono</td>
<td>Dirección</td>
</tr>
<tr><!--creamos otra fila-->
<td></td> <!--Aquí se cargarán los datos correspondientes-->
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table> <!--Fin de la tabla-->
</div> <!--Cierre del div de la izquierda-->
<div style="width: 40%; float:right; text-align:center;"> <!--Div de la derecha-->
<h2>Personas</h2> <!--Titulo-->
<table border = "1" style = "width: 100%;"><!--Tabla de la derecha-->
{% for (index, persona) in Todos.items() %}
<tr><!--Fila-->
<td>{{persona.nombre}}, {{persona.apellido}}</td><!--Columna que mostrará el nombre y apellido-->
<td><button name = "ver">Ver</button></td><!--Columna que mostrará el botón Ver-->
<td><button name = "editar">Editar</button></td><!--Columna que mostrará el botón Editar-->
<td><button name = "eliminar">Eliminar</button></td><!--Columna que mostrará el botón Eliminar-->
</tr>
{% endfor %}
<tr>
<td><button name = "agregar">Agregar</button></td><!--Columna que mostrará el botón Agregar-->
</tr>
</table>
</div>
</div>
</body>
</html>
Y este será el resultado:

Ahora nos quedaría añadir a cada uno de los botones la llamada al método correspondiente.
Botones de acción en nuestro template index.html
En el bucle for anterior he accedido al index como la clave y asignado cada respectivo valor a persona. Entonces en cada botón podremos usar el número de index para llamar al método dependiendo la persona a la que corresponde ese botón presionado. Lo verás a continuación:

El botón «Ver» – Mostrar elementos (GET) – READ
En el caso de este botón es realmente sencillo ya que solo estaremos solicitando información al servidor mediante GET e intentaremos mostrarla en nuestro divisorio de la derecha. Como ya tenemos cargados los elementos en la variable Todos simplemente podríamos utilizar el numero de index del elemento y mostrar sus valores según el atributo de la persona. Pero para ello, debemos:
- Crear un bucle for similar al anterior en
index.htmlque compruebe mediante un condicional el número de index pasado como argumento con el diccionarioTodosy muestre los datos del elemento en cuestión. - Crear una url dinámica que nos permita pasar el numero de index del elemento a mostrar como argumento.
- Mediante el botón «ver» debemos redirigir al cliente a esa ruta seguido del número de index.
Como primer paso, nos bastaría con utilizar un bucle for similar al anterior pero esta vez introduciendo un condicional que compruebe una variable index_ver que se corresponderá al index (indice id) de la persona o elemento de la lista que queremos mostrar osea, de la que presionamos el botón ver. Algo así:
<!DOCTYPE html>
<html lang="es"> <!--Cambiamos a español-->
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mi primer App en Flask</title> <!--Modificamos el Título-->
</head>
<body><!--Aquí comienza el cuerpo de nuestro HTML-->
<h1 style = "text-align:center">Directorio Telefónico - Mi primer aplicación en Flask</h1> <!--Titulo principal centrado-->
<br>
<div> <!--Creamos un div contenedor-->
<div style="width: 60%; float:left; text-align:center;"> <!--Creamos el div de la izquierda-->
<h2>Datos completos de la persona</h2> <!--Titulo-->
<table border = "1" style = "width: 100%;"> <!--Creamos la tabla con borde-->
<tr><!--Creamos una fila-->
<td>Nombre</td> <!--Dentro de la fila las columnas-->
<td>Apellido</td>
<td>Apodo</td>
<td>Teléfono</td>
<td>Dirección</td>
</tr>
{% for (index, persona) in Todos.items() %}
{% if index == index_ver %}
<tr><!--creamos otra fila-->
<td>{{persona.nombre}}</td> <!--Aquí se cargarán los datos correspondientes-->
<td>{{persona.apellido}}</td>
<td>{{persona.apodo}}</td>
<td>{{persona.telefono}}</td>
<td>{{persona.direccion}}</td>
</tr>
{% endif %}
{% endfor %}
</table> <!--Fin de la tabla-->
</div> <!--Cierre del div de la izquierda-->
<div style="width: 40%; float:right; text-align:center;"> <!--Div de la derecha-->
<h2>Personas</h2> <!--Titulo-->
<table border = "1" style = "width: 100%;"><!--Tabla de la derecha-->
{% for (index, persona) in Todos.items() %}
<tr><!--Fila-->
<td>{{persona.nombre}}, {{persona.apellido}}</td><!--Columna que mostrará el nombre y apellido-->
<td><button name = "ver">Ver</button></td><!--Columna que mostrará el botón Ver-->
<td><button name = "editar">Editar</button></td><!--Columna que mostrará el botón Editar-->
<td><button name = "eliminar">Eliminar</button></td><!--Columna que mostrará el botón Eliminar-->
</tr>
{% endfor %}
<tr>
<td><button name = "agregar">Agregar</button></td><!--Columna que mostrará el botón Agregar-->
</tr>
</table>
</div>
</div>
</body>
</html>
Ahora para ejemplificar si en nuestro controlador en la función render_template() pasamos un valor entero a la variable index_ver por ejemplo «0» (cero) mostrará el nombre de la persona cuya clave es «0» en el diccionario Todos. (Aclaración: en el bucle for estamos iterando un diccionario por el par Clave : Valor donde el index es la clave, y valor persona). Ejemplo:
@app.route('/', methods = ['GET']) #Decoramos con método de app que es una instancia de la clase Flask y argumentamos "slash"
def inicio(): #Definimos una función llamada Inicio
#Llamamos al método para listar personas y lo almacenamos en "Todos"
Todos = Persona.leer_contacto('id', 'all')
return render_template('/index.html', Todos = Todos, index_ver = 0) #Retornornamos el template + Todos + numero de id
Si pasamos el valor «0» (cero) se mostrará:

Así que vamos por el camino correcto, podríamos creer ahora que solo nos faltaría pasar determinado valor a esta variable cuando el usuario presione alguno de los botones «ver» de la izquierda. Pero..
La ruta «index» no es dinámica, es una ruta estática. Por lo que es mejor crear otra ruta aparte que sea dinámica.
Así que borra el código index_ver = 0 que estábamos pasando en el inicio. Y vamos a ver como hacerlo correctamente. Pero antes debemos aprender bien la diferencia entre los distintos tipos de rutas / url’s que podemos crear con nuestro controlador en Flask:
Rutas estáticas y rutas dinámicas – URL’S en Flask
En este caso sucede que inicio es la ruta principal de nuestra aplicación por lo que es una url estática y no podemos convertirla en dinámica o bueno en criollo «no deberíamos» ya que el cliente espera acceder a esta url directamente para encontrarse con la página de inicio.
Una url dinámica en cambio incluye argumentos en su estructura que normalmente ordenan al servidor realizar algún tipo de procesamiento de datos para mostrar un resultado.
Atención: Y no hay que confundir URL estática o dinámica con Rutas estáticas o dinámicas. Cuando hablamos de url nos referimos a la dirección ingresada en el navegador, como una cadena de caracteres. Pero si nos referimos a rutas lo hacemos a la parte de enrutamiento que responde a esa solicitud desde el lado del servidor. De nuestras rutas en el controlador.
Así que vamos a nuestro controlador y creamos una ruta dinámica que nos permita pasar el argumento index y en ella vamos a cargar el template index.html igualmente como lo hacemos en la ruta inicio. Esta ruta debe ser:
- Debe admitir solo método GET.
- Debe permitir pasar un argumento de número entero (será el id del elemento a mostrar completo).
- La función que ejecuta esa ruta debe tener un parámetro para recibir ese argumento.
Fíjate bien como lo hacemos:
@app.route('/ver/<int:index>', methods = ['GET'])
def ver(index): #Definimos una función llamada Ver
Todos = Persona.leer_contacto('id', 'all')
return render_template('/index.html', Todos = Todos, index_ver = index) #Retornornamos el template + Todos
Estamos definiendo la url /ver/[un número entero]. Por lo que al acceder a 127.0.0.1:5000/ver/0 tomará el cero como argumento para el parámetro index.
- Definimos la función
ver(index)que será la función que ejecute esta ruta, y en ella también creamos el parámetro index para recibir dicho argumento. - Finalmente debemos pasar igualmente como lo hacíamos en
inicioel diccionarioTodoscon los elementos para que se pueda cargar los elementos en la columna izquierda. - Y también pasamos que la variable
index_ver(que es la que definimos en el bucle for en index.html) será igual a este parámetro pasado en la url.
Ahora te vas a dicha url y voilá:

Ahora solo nos falta añadir a los botones «Ver» la url que acabamos de crear enviando el valor de index como argumento para la variable index_ver. Y necesitaremos utilizar url_for() el cual básicamente nos permite crear «link’s». Fíjate:
Nuestro index.html queda así:
<!DOCTYPE html>
<html lang="es"> <!--Cambiamos a español-->
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mi primer App en Flask</title> <!--Modificamos el Título-->
</head>
<body><!--Aquí comienza el cuerpo de nuestro HTML-->
<h1 style = "text-align:center">Directorio Telefónico - Mi primer aplicación en Flask</h1> <!--Titulo principal centrado-->
<br>
<div> <!--Creamos un div contenedor-->
<div style="width: 60%; float:left; text-align:center;"> <!--Creamos el div de la izquierda-->
<h2>Datos completos de la persona</h2> <!--Titulo-->
<table border = "1" style = "width: 100%;"> <!--Creamos la tabla con borde-->
<tr><!--Creamos una fila-->
<td>Nombre</td> <!--Dentro de la fila las columnas-->
<td>Apellido</td>
<td>Apodo</td>
<td>Teléfono</td>
<td>Dirección</td>
</tr>
{% for (index, persona) in Todos.items() %}
{% if index == index_ver %}
<tr><!--creamos otra fila-->
<td>{{persona.nombre}}</td> <!--Aquí se cargarán los datos correspondientes-->
<td>{{persona.apellido}}</td>
<td>{{persona.apodo}}</td>
<td>{{persona.telefono}}</td>
<td>{{persona.direccion}}</td>
</tr>
{% endif %}
{% endfor %}
</table> <!--Fin de la tabla-->
</div> <!--Cierre del div de la izquierda-->
<div style="width: 40%; float:right; text-align:center;"> <!--Div de la derecha-->
<h2>Personas</h2> <!--Titulo-->
<table border = "1" style = "width: 100%;"><!--Tabla de la derecha-->
{% for (index, persona) in Todos.items() %}
<tr><!--Fila-->
<td>{{persona.nombre}}, {{persona.apellido}}</td><!--Columna que mostrará el nombre y apellido-->
<td><a href="{{url_for('ver', index = index)}}"><button name = "ver">Ver</button></a></td><!--Columna que mostrará el botón Ver-->
<td><button name = "editar">Editar</button></td><!--Columna que mostrará el botón Editar-->
<td><button name = "eliminar">Eliminar</button></td><!--Columna que mostrará el botón Eliminar-->
</tr>
{% endfor %}
<tr>
<td><button name = "agregar">Agregar</button></td><!--Columna que mostrará el botón Agregar-->
</tr>
</table>
</div>
</div>
</body>
</html>
Y este es el resultado:

Entonces en un diagrama lo podemos entender más o menos asi.
Para que recuerdes: Esto que hicimos aquí fue crear una url dinámica para mostrar datos basados en un indice y lo enlazamos a un botón que obtiene este indice directamente del listado de elementos para poder filtrar los datos adecuados al volver a cargar la plantilla index.html
Botón «Editar» – Formulario HTML Jinja2 – Modificar datos
Pero lo cierto es que para el botón editar necesitaremos desplegar un formulario que permita ingresar nuevos datos al cliente pero también nos permita ver los anteriores a modificar. Así que lo primero sería crear un formulario para dicha acción, en este caso haremos uso de HTML puro, pero estaremos procesando los datos haciendo uso de la solicitudes POST y GET en una misma url.
- Vamos a crearlo puramente en HTML sin librerías. Este form tendrá el método action apuntando a nuestra ruta
/editardonde modificaremos el elemento. - En el controlador debemos procesar los datos del formulario, por lo que añadiremos la ruta
editary en ella debemos añadir un condicional que compruebe si se trata de la solicitud GET o POST. - Por último en nuestro controlador configuramos la edición de el elemento a modificar en la base de datos JSON usando el método que creamos para ello
actualizar_contacto().
Insertar formulario HTML en una plantilla Jinja2
Para crear un formulario en flask debemos tener un mínimo conocimiento de HTML previo pero considero que ahora con una explicación básica bastará.
<form action="/editar/0" method="post">
<table border="1">
<tr>
<td>Nombre</td>
<td>Apellido</td>
<td>Apodo</td>
<td>Teléfono</td>
<td>Dirección</td>
</tr>
<tr>
<input type="hidden" name="id" value="0">
<td><input type="text" name="Nombre" value="Mariano" style="max-width: 100px;"></td>
<td><input type="text" name="Apellido" value="Laca" style="max-width: 100px;"></td>
<td><input type="text" name="Apodo" value="Marianito" style="max-width: 100px;"></td>
<td><input type="tel" name="Telefono" value="11-2345-6789" style="max-width: 100px;"></td>
<td><input type="text" name="Direccion" value="Calle Falsa 123, Buenos Aires" style="max-width: 100px;"></td>
</tr>
</table>
<br>
<button type="submit">Modificar</button>
</form>
Este es un formulario HTML básico que al ser ejecutado el botón «Modificar» llamaría a la función que tiene definida en «action«. Normalmente los formularios se desarrollan igual en el frontend pero procesan los datos ingresados por detrás haciendo uso del lenguaje backend en este caso Python.
Si nosotros queremos insertar este formulario debemos «considerar» aprovechar las funciones Jinja2 en la plantilla templates/editar_contacto.html para pesarle los argumentos correctos de los datos que ya cargamos en la plantilla enviados por nuestro controlador en la ruta /editar y el llamado a la función de «Modificar» los datos de nuestra base de datos.
- En el form action debemos colocar la url correspondiente a la función que vamos a llamar para procesar los datos cuando los enviemos con la solicitud POST:
-
-
form action=»{{url_for(‘editar’, index = index_ver)}}» method=»post»
-
-
- Debemos usar un input de tipo «hidden» (oculto) para registrar el «id» o clave primaria del registro que estamos editando (como cargamos todas las personas se asignara el id de la correspondiente):
-
<input type=»hidden» name=»id» value=»{{Todos[index_ver].id}}«>
-
- En los input debemos rellenar con el dato existente por defecto y brindar la posibilidad de modificar el valor..
-
<input type=»text» name=»Nombre» value=»{{Todos[index_ver].nombre}}» style=»max-width: 100px;»>
-
Así que un formulario limpio se vería así:
<form action="{{url_for('editar', index = index_ver)}}" method="post">
<table border="1" style="width: 80%; margin: 0 auto;">
<tr>
<td>Nombre</td>
<td>Apellido</td>
<td>Apodo</td>
<td>Teléfono</td>
<td>Dirección</td>
</tr>
<tr>
<input type="hidden" name="id" value="{{Todos[index_ver].id}}">
<td><input type="text" name="Nombre" value="{{Todos[index_ver].nombre}}" style="max-width: 100px;"></td>
<td><input type="text" name="Apellido" value="{{Todos[index_ver].apellido}}" style="max-width: 100px;"></td>
<td><input type="text" name="Apodo" value="{{Todos[index_ver].apodo}}" style="max-width: 100px;"></td>
<td><input type="tel" name="Telefono" value="{{Todos[index_ver].telefono}}" style="max-width: 100px;"></td>
<td><input type="text" name="Direccion" value="{{Todos[index_ver].direccion}}" style="max-width: 100px;"></td>
</tr>
</table>
<br>
<button type="submit">Modificar</button>
</form>
El código HTML de nuestro formulario y la ruta «editar» en el controlador
Para nuestro formulario simplemente vamos a copiar el código de index.html y vamos a insertar el formulario de edición aprovechando que obtenemos los datos en el bucle for de Jinja2. Entonces al momento de presionar el botón «editar» en algún elemento de la lista de la columna derecha nos aparecerá este formulario.
Nuestro editar_contacto.html necesita ahora también que definamos una ruta en nuestro controlador:
- La ruta «editar» debe permitir método GET y POST.
- Además, si se trata de GET debe solo mostrar los datos del formulario.
- Pero, si se trata de una solicitud POST debe «obtener» los datos ingresados en el formulario y llamar al método
actualizar_contacto()de nuestro modelo pasando estos atributos a modificar y luego debe guardarlos nuevamente en la DB/base_de_datos.json.
Nuestro templates/editar_contacto.html lo hacemos modificando el index.html pero incluyendo el formulario:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mi primer App en Flask</title>
</head>
<body>
<h1 style="text-align:center">Directorio Telefónico - Mi primer aplicación en Flask</h1>
<br>
<!-- Contenedor principal: tabla izquierda + lista derecha -->
<div>
<!-- DIV IZQUIERDA: muestra los datos de la persona a editar -->
<div style="width: 60%; float:left; text-align:center;">
<h2>Datos completos de la persona</h2>
<table border="1" style="width: 100%;">
<tr>
<td>Nombre</td>
<td>Apellido</td>
<td>Apodo</td>
<td>Teléfono</td>
<td>Dirección</td>
</tr>
{% for (index, persona) in Todos.items() %}
{% if index == index_ver %}
<tr>
<td>{{persona.nombre}}</td>
<td>{{persona.apellido}}</td>
<td>{{persona.apodo}}</td>
<td>{{persona.telefono}}</td>
<td>{{persona.direccion}}</td>
</tr>
{% endif %}
{% endfor %}
</table>
</div>
<!-- DIV DERECHA: lista de todas las personas -->
<div style="width: 40%; float:right; text-align:center;">
<h2>Personas</h2>
<table border="1" style="width: 100%;">
{% for (index, persona) in Todos.items() %}
<tr>
<td>{{persona.nombre}}, {{persona.apellido}}</td>
<td><a href="{{url_for('ver', index = index)}}"><button name="ver">Ver</button></a></td>
<td><a href="{{url_for('editar', index = index)}}"><button name="editar">Editar</button></a></td>
<td><button name="eliminar">Eliminar</button></td>
</tr>
{% endfor %}
<tr>
<td><button name="agregar">Agregar</button></td>
</tr>
</table>
</div>
</div>
<!-- SECCIÓN DE EDICIÓN (nueva, debajo de todo) -->
<div style="clear:both; text-align:center; margin-top:40px; border-top:2px solid #ccc; padding-top:20px;">
<h2>Modificar Persona</h2>
<h4>Base de datos JSON (Index = {{index_ver}} ID = {{Todos[index_ver].id}})</h4>
<form action="{{url_for('editar', index = index_ver)}}" method="post">
<table border="1" style="width: 80%; margin: 0 auto;">
<tr>
<td>Nombre</td>
<td>Apellido</td>
<td>Apodo</td>
<td>Teléfono</td>
<td>Dirección</td>
</tr>
<tr>
<input type="hidden" name="id" value="{{Todos[index_ver].id}}">
<td><input type="text" name="Nombre" value="{{Todos[index_ver].nombre}}" style="max-width: 100px;"></td>
<td><input type="text" name="Apellido" value="{{Todos[index_ver].apellido}}" style="max-width: 100px;"></td>
<td><input type="text" name="Apodo" value="{{Todos[index_ver].apodo}}" style="max-width: 100px;"></td>
<td><input type="tel" name="Telefono" value="{{Todos[index_ver].telefono}}" style="max-width: 100px;"></td>
<td><input type="text" name="Direccion" value="{{Todos[index_ver].direccion}}" style="max-width: 100px;"></td>
</tr>
</table>
<br>
<button type="submit">Modificar</button>
</form>
</div>
</body>
</html>
Añadimos la ruta «editar» en nuestro controlador por ahora así:
@app.route('/editar/<int:index>', methods = ['GET', 'POST']) #
def editar(index): #Definimos una función llamada Editar
Todos = Persona.leer_contacto('id', 'all')
if request.method == 'POST':
pass
else:
pass
return render_template('/editar_contacto.html', Todos = Todos, index_ver = index) #Retornornamos el template + Todos
Ahora si ingresamos a la url: 127.0.0.1/editar/1 obtenemos:
Modifiquemos el botón editar en «index.html» y también en «editar_contacto.html» para que apunten correctamente:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mi primer App en Flask</title>
</head>
<body>
<h1 style="text-align:center">Directorio Telefónico - Mi primer aplicación en Flask</h1>
<br>
<div>
<!-- DIV IZQUIERDA -->
<div style="width: 60%; float:left; text-align:center;">
<h2>Datos completos de la persona</h2>
<table border="1" style="width: 100%;">
<tr>
<td>Nombre</td>
<td>Apellido</td>
<td>Apodo</td>
<td>Teléfono</td>
<td>Dirección</td>
</tr>
{% for (index, persona) in Todos.items() %}
{% if index == index_ver %}
<tr>
<td>{{persona.nombre}}</td>
<td>{{persona.apellido}}</td>
<td>{{persona.apodo}}</td>
<td>{{persona.telefono}}</td>
<td>{{persona.direccion}}</td>
</tr>
{% endif %}
{% endfor %}
</table>
</div>
<!-- DIV DERECHA -->
<div style="width: 40%; float:right; text-align:center;">
<h2>Personas</h2>
<table border="1" style="width: 100%;">
{% for (index, persona) in Todos.items() %}
<tr>
<td>{{persona.nombre}}, {{persona.apellido}}</td>
<td><a href="{{url_for('ver', index = index)}}"><button name="ver">Ver</button></a></td>
<td><a href="{{url_for('editar', index = index)}}"><button name="editar">Editar</button></a></td> <!-- Conectado a editar -->
<td><button name="eliminar">Eliminar</button></td> <!-- Sin url_for -->
</tr>
{% endfor %}
<tr>
<td><button name="agregar">Agregar</button></td> <!-- Sin url_for -->
</tr>
</table>
</div>
</div>
</body>
</html>
En nuestro index.html para el botón VER utilizamos:
<a href=»{{url_for(‘ver’, index = index)}}«><button name=»ver»>Ver</button></a>Que se traduce:127.0.0.1/ver/<int:index>
Y para el botón EDITAR:
<a href=»{{url_for(‘editar’, index = index)}}«><button name=»editar»>Editar</button></a>Que se traduce:127.0.0.1/editar/<int:index>
Además de los campos tradicionales quería resaltar que hemos añadido el valor por defecto que sea igual al que ya se encuentra almacenado en la base de datos, entonces luego al obtener los datos del formulario en el controlador el cambio solo se producirá en el elemento modificado y los demás se guardar igual a como estaban.
Y me refiero al elemento value que es aquel que el formulario asume por defecto.
Como ves el action de nuestro form conduce a la función de la ruta editar + el index_ver por lo que ahora debemos procesar los datos que el usuario ingrese desde el controlador en la ruta editar dentro del condicional POST.
¿Y ahora? – Solo nos queda procesar los datos del formulario haciendo una solicitud POST, pero antes vamos a profundizar un poco en los dos tipos de solicitudes y sus diferencias.
¿Qué son GET y POST? – Los métodos HTTP en Flask
Antes de continuar con el procesamiento del formulario, es importante entender los dos métodos HTTP que vamos a usar en nuestra aplicación: GET y POST.
- GET se utiliza para solicitar datos del servidor. Cuando escribes una URL en el navegador, estás haciendo una petición GET. En nuestra app, usamos GET para cargar la página de inicio y para mostrar los datos de una persona específica (por ejemplo,
/ver/1). - POST se utiliza para enviar datos al servidor, generalmente desde un formulario. Cuando el usuario modifica un contacto y presiona «Modificar», los datos del formulario viajan en una petición POST. El servidor los procesa, actualiza la base de datos y devuelve una respuesta.
En Flask, podemos hacer que una misma ruta responda a ambos métodos. Por ejemplo, nuestra ruta /editar/<int:index>:
- Responde a GET cuando el usuario accede a la URL (por ejemplo,
/editar/1). En ese caso, mostramos el formulario con los datos actuales de la persona. - Responde a POST cuando el usuario envía el formulario modificado. En ese caso, procesamos los nuevos datos y los guardamos en la base de datos.
El siguiente diagrama ilustra el flujo completo de ambas peticiones en nuestra aplicación Flask:

Más adelante, en futuras entradas de esta serie, profundizaremos en otros métodos como PUT y DELETE, y en cómo trabajar con APIs REST. Por ahora, con GET y POST tenemos suficiente para construir nuestra aplicación CRUD.
Procesando los datos del formulario enviados con Requests.form() en el controlador
Cuando el usuario completa el formulario y presiona el botón «Modificar», el navegador envía una petición POST a la misma URL /editar/<index>. Pero esta vez, el servidor no solo debe mostrar el formulario, sino procesar los datos enviados. Veamos cómo Flask nos permite hacerlo en nuestra ruta editar:
@app.route('/editar/<int:index>', methods = ['GET', 'POST']) #
def editar(index): #Definimos una función llamada Editar
Todos = Persona.leer_contacto('id', 'all')
if request.method == 'POST': #Si el método es POST
#Obtenemos los datos del formulario y los almacenamos en variables
id = int(request.form['id']) #Obtenemos "id" y lo convertimos en entero
Nombre = request.form['Nombre']
Apellido = request.form['Apellido']
Apodo = request.form['Apodo']
Telefono = request.form['Telefono']
Direccion = request.form['Direccion']
#Ejecutar el método actualizar_contacto() usando el "id"
Persona.actualizar_contacto(id, 'nombre', Nombre)
Persona.actualizar_contacto(id, 'apellido', Apellido)
Persona.actualizar_contacto(id, 'apodo', Apodo)
Persona.actualizar_contacto(id, 'telefono', Telefono)
Persona.actualizar_contacto(id, 'direccion', Direccion)
else:
pass
return render_template('/editar_contacto.html', Todos = Todos, index_ver = index) #Retornornamos el template + Todos
request.formes un diccionario especial que Flask pone a nuestra disposición. Contiene todos los datos que el formulario envió en la petición POST, utilizando los nombres (name) que definimos en los campos<input>.id = int(request.form['id']): Obtenemos el valor del campo oculto (input type="hidden") que contiene el identificador único de la persona. Lo convertimos a entero porque en la base de datos es un número.- Luego, obtenemos el resto de los campos:
Nombre,Apellido,Apodo,TelefonoyDireccion. Todos estos valores llegan como cadenas de texto (str), que es exactamente lo que necesitamos para actualizar la base de datos. Persona.actualizar_contacto(id, 'nombre', Nombre): Aquí llamamos al método del modelo que se encarga de modificar un atributo específico de la persona con eliddado. En el modelo, este método actualiza el archivo JSON y lo guarda. Hacemos una llamada por cada atributo a modificar (nombre, apellido, etc.).
Una vez que todos los cambios se han guardado en la base de datos, el controlador vuelve a cargar la misma plantilla editar_contacto.html con los datos actualizados, de modo que el usuario vea el formulario con los nuevos valores. Esto se logra con return render_template(...) al final de la función.
Si la petición es GET (es decir, el usuario simplemente accede a la URL sin enviar el formulario), el condicional if request.method == 'POST' no se ejecuta, y el controlador solo muestra el formulario con los datos actuales (sin realizar ninguna modificación).
- Hemos creado la función
editar()y le hemos pasado como parámetroindexque recibirá el número de index del elemento que queremos modificar. - Seguido se comprobará mediante un condicional si se trata de una solicitud POST y de ser así se ejecutará el bloque de código dentro del condicional.
- Dicho bloque de código obtiene los datos del formulario HTML que creamos para editar elementos en la base de datos, entre ellos el
idque lo estamos pasando como dato oculto a la vista del usuario. Más que oculto, sería decir que no se lo estamos pidiendo, sino que lo obtenemos. Estos datos obtenidos del formulario son almacenados dentro de variables que tienen el mismo nombre que el atributo pero comienzan con mayúsculas. - Finalmente llamamos al método
actualizar_contacto()que se encargará como definimos en el modelo de almacenar los cambios en la base de datos JSON. [En el modelo hubiéramos podido programar el método para recibir todos los atributos y valores a la vez, mala mía. Tu puedes corregirlo para evitar llamar una vez al método por cada atributo y valor a modificar.] - Como ves en caso de no tratarse de una solicitud POST simplemente escapa al condicional y carga el sitio normalmente como solicitud GET retornando el template y las variables
Todos,index.
Botón «Agregar» – Añadir elementos (POST) – CREATE
Una vez que tenemos funcionando la edición de contactos, el siguiente paso lógico es permitir al usuario crear nuevos registros. Para ello, seguiremos el mismo patrón que ya hemos usado en el botón «Editar», pero adaptándolo para crear en lugar de modificar.
El proceso es muy similar:
- Crearemos una nueva plantilla HTML (
agregar_contacto.html) con un formulario vacío. - Definiremos una nueva ruta en el controlador (
/agregar) que maneje tanto GET (mostrar el formulario) como POST (procesar los datos y guardar el nuevo contacto). - Modificaremos el botón «Agregar» en
index.htmlpara que apunte a esta nueva ruta.
Veamos los pasos en detalle:
Paso 1: Crear la plantilla agregar_contacto.html
Para el formulario de creación, no queremos que los campos vengan con valores predeterminados (como ocurría en la edición). En su lugar, utilizaremos el atributo placeholder para mostrar un texto orientativo dentro de cada campo. Esto es más amigable porque el usuario no tiene que borrar nada, simplemente escribe sobre el espacio en blanco.
Además, eliminamos la tabla de «Datos completos» que teníamos en editar_contacto.html, ya que aquí no hay un contacto existente que mostrar.
El formulario tendrá el action apuntando a la ruta agregar (usando url_for) y el método POST.
El código completo de agregar_contacto.html es el siguiente:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mi primer App en Flask</title>
</head>
<body>
<h1 style="text-align:center">Directorio Telefónico - Mi primer aplicación en Flask</h1>
<br>
<div>
<div style="width: 60%; float:left; text-align:center;">
<h2>Agregar Persona</h2>
<table border="1" style="width: 100%;">
<tr>
<td>Nombre</td>
<td>Apellido</td>
<td>Apodo</td>
<td>Teléfono</td>
<td>Dirección</td>
</tr>
<form action="{{url_for('agregar')}}" method="post">
<tr>
<td><input type="text" name="Nombre" placeholder="Nombre" style="max-width: 100px;"></td>
<td><input type="text" name="Apellido" placeholder="Apellido" style="max-width: 100px;"></td>
<td><input type="text" name="Apodo" placeholder="Apodo" style="max-width: 100px;"></td>
<td><input type="tel" name="Telefono" placeholder="Teléfono" style="max-width: 100px;"></td>
<td><input type="text" name="Direccion" placeholder="Dirección" style="max-width: 100px;"></td>
</tr>
</table>
<br>
<button type="submit">Agregar</button>
</form>
</div>
<div style="width: 40%; float:right; text-align:center;">
<h2>Personas</h2>
<table border="1" style="width: 100%;">
{% for (index, persona) in Todos.items() %}
<tr>
<td>{{persona.nombre}}, {{persona.apellido}}</td>
<td><a href="{{url_for('ver', index = index)}}"><button name="ver">Ver</button></a></td>
<td><a href="{{url_for('editar', index = index)}}"><button name="editar">Editar</button></a></td>
<td><button name="eliminar">Eliminar</button></td>
</tr>
{% endfor %}
<tr>
<td><a href="{{url_for('agregar')}}"><button name="agregar">Agregar</button></a></td>
</tr>
</table>
</div>
</div>
</body>
</html>
Observa que la tabla derecha es idéntica a la de index.html y editar_contacto.html, de modo que siempre listamos los contactos disponibles. El botón «Agregar» ahora también usa url_for('agregar') para dirigir al formulario de creación.
Paso 2: Añadir la ruta «agregar» en el controlador
Al igual que con la edición, nuestra ruta /agregar responderá a dos métodos: GET para mostrar el formulario vacío, y POST para procesar los datos enviados.
Cuando el usuario envía el formulario (método POST):
- Obtenemos los datos mediante
request.form, igual que en la edición. - Creamos una nueva instancia de la clase
Personacon los datos recibidos. - Llamamos al método
crear_contacto()del modelo, que se encarga de guardar el nuevo registro en la base de datos JSON. - Después de guardar, hacemos un
redirect('agregar')para recargar la página y mostrar la lista actualizada con el nuevo contacto.
El uso de redirect tiene una ventaja importante: evita que el usuario reenvíe el formulario si actualiza la página (el famoso mensaje «Confirmar reenvío del formulario»). Al redirigir a la misma URL pero con método GET, la página se recarga limpiamente y la lista se actualiza.
from flask import redirect
El código de la ruta agregar es el siguiente:
from flask import redirect
@app.route('/agregar', methods = ['GET','POST'])
def agregar():
Todos = Persona.leer_contacto('id', 'all')
if request.method == 'POST':
# Obtenemos los datos del formulario
Nombre = request.form['Nombre']
Apellido = request.form['Apellido']
Apodo = request.form['Apodo']
Telefono = request.form['Telefono']
Direccion = request.form['Direccion']
# Creamos una nueva persona y la guardamos
Nuevo_contacto = Persona(Nombre, Apellido, Apodo, Telefono, Direccion)
Nuevo_contacto.crear_contacto()
# Redirigimos a la misma página para ver el contacto añadido
return redirect('agregar')
else:
pass
return render_template('/agregar_contacto.html', Todos = Todos)
Con esto, el flujo completo de creación queda así:
- El usuario hace clic en «Agregar» desde cualquier vista.
- Se muestra el formulario vacío (GET).
- El usuario completa los campos y envía (POST).
- El controlador procesa los datos, crea el contacto y lo guarda en el JSON.
- Se redirige a la misma página (GET), que ahora muestra la lista actualizada con el nuevo contacto.
De esta manera, completamos la operación CREATE de nuestro CRUD. Es momento de verificar que todo funcione correctamente, si obtienes algún error comenta en el post, no olvides importar las librerías necesarias como redirect().
Resultado:

Con estas adiciones, nuestra aplicación ahora permite añadir nuevos contactos. Al hacer clic en «Agregar», veremos un formulario limpio. Después de enviarlo, el contacto aparecerá en la lista de la derecha y podremos ver sus detalles, editarlo o eliminarlo.
El siguiente paso será implementar la operación DELETE (Eliminar) para completarla.
No olvides completar el link Agregar en el index.html a
{{url_for('agregar')}}
Botón «Eliminar» – Eliminar elementos – DELETE
La última operación del CRUD es la eliminación. En nuestro caso, la haremos de la forma más sencilla: usando el método GET y pasando el id del contacto a eliminar en la URL.
¿Por qué usamos GET y no DELETE? En teoría, el método HTTP adecuado para eliminar es DELETE. Sin embargo, los navegadores no permiten enviar peticiones DELETE directamente desde un enlace o un formulario HTML estándar (solo soportan GET y POST). Por simplicidad y para mantener el tutorial accesible, usaremos GET. Más adelante, cuando veamos JavaScript y AJAX, podremos implementar el método DELETE correctamente.
Para implementar la eliminación, solo necesitamos:
- Añadir un enlace en el botón «Eliminar» de
index.htmlque apunte a la ruta/eliminar/<id>. - Crear una ruta en el controlador que reciba el
id, llame al métodoeliminar_contacto()del modelo, y redirija a la página principal para ver la lista actualizada.
El código de la ruta es muy sencillo:
@app.route('/eliminar/<int:id>', methods=['GET'])
def eliminar(id):
Persona.eliminar_contacto(id)
return redirect('/')
Como ves, no necesitamos verificar el método porque la ruta solo acepta GET. Simplemente eliminamos el contacto y redirigimos a la raíz.
Una nota importante: En una aplicación real, eliminar un recurso con GET no es recomendable por razones de seguridad (los motores de búsqueda podrían rastrear esos enlaces y eliminar contenido sin querer). Lo ideal sería usar un formulario con método POST o, mejor aún, una petición AJAX con DELETE. Pero para este tutorial, es suficiente para entender el concepto.
Resultado:
- CREATE →
agregar - READ →
vereinicio - UPDATE →
editar - DELETE →
eliminar
¡Nuestra primera aplicación Flask con CRUD funcional está terminada!

Código completo explicado de Nuestra primer aplicación Flask + CRUD

Si este tutorial te ha sido útil, compártelo en tus redes sociales para que más personas puedan aprender a desarrollar con Flask y Jinja2.
Y no olvides suscribirte al blog para recibir todas las novedades, nuevos tutoriales y contenido exclusivo directamente en tu correo.
📚 ¿Qué viene después?
En el próximo post haremos un resumen completo de Flask con toda la teoría, práctica y ejemplos que necesitas para dominar el framework.
Repasaremos desde la configuración del entorno virtual y el uso de Git, hasta la arquitectura MVC, Blueprints y la sintaxis de Jinja2 paso a paso.
¡No te lo pierdas!
🐍 ¡Gracias por llegar hasta aquí! Nos vemos en la próxima entrega.
📅 Última actualización: julio 17, 2026


