Jinja2 – Vistas + Modelo & Controlador | Tutorial Flask (Parte 4)

Indice del contenido del post

 

Bienvenido a la cuarta parte de esta serie para aprender a crear aplicaciones Flask.Curso de python 3: Python Total en 16 días

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.

 

Jinja2 - Aplicaciones flask con python MVC

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

Código completo del Proyecto + Repositorio
Información de la aplicación
Este es el código base que construimos en la parte anterior de la serie. Contiene el modelo con la lógica CRUD sobre JSON, el controlador con las rutas básicas y la vista principal. Lo recordamos aquí para tenerlo a mano antes de comenzar a conectar todo con Jinja2.
models.py
#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
    def leer_contacto(atr, valor):
        encontradas = {} #Creamos un diccionario para almacenar los resultados

        for persona in datos["Personas"]: #Cada "indice" es un diccionario de persona
            if persona[atr] == valor: #Si el atributo de ese diccionario es igual al que pasamos


                #Obtenemos el indice de esa persona
                indice = datos["Personas"].index(persona) #La variable toma el valor de la posición de la persona

                #Guardamos en el diccionario nuevo
                encontradas[indice] = persona #Se añade cada persona encontrada con index como clave

        return (encontradas) #Retornamos los datos encontrados


    #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()
controlador.py
from flask import Flask, request, render_template #Importamos clase Flask, objeto request y función render_template de flask (librería)
app = Flask(__name__) #Creamos una app instanciando la clase Flask (automáticamente el nombre de la app)

#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
index.html
<!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: 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-->
                <tr><!--Fila-->
                    <td></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>
                <tr>
                    <td><button name = "agregar">Agregar</button></td><!--Columna que mostrará el botón Agregar-->
                </tr>
            </table>
        </div>
    </div>
</body>
</html>

 

 

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):

controlador.py – 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.

captura-aplicacion-jinja2-tutorial-flask

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.

Listar datos de la base de datos JSON al detectar una solicitud get - jinja2 . tutorial flask

 

¿Cómo listamos los elementos (Personas) de la base de datos JSON en nuestra plantilla index.html?

meme python pensarBueno 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:

  1. Llamar al método del modelo leer_contacto() desde el controlador respondiendo solicitud GET.
  2. 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.
  3. 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:

controlador.py – Versión con lectura de datos
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:

 

Jinja2 - Logotipo

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) donde var corresponde a la variable y valor a 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í:

index.html – Primer listado con Jinja2
<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í:

Jinja2 - Python Flask listar todos los elementos usando CRUD

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í:

index.html – Listado mejorado (solo nombre y apellido)
<!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:

Jinja2 - Listar datos de un diccionario

Recordemos:
Aquí estamos trayendo todas las personas del diccionario que es nuestra base de datos y filtrando en la vista solo el Nombre y Apellido, y traemos el diccionario completo porque igualmente vamos a mostrar los datos completos de cada persona a la derecha. Quedando entonces una lista de contactos a la derecha y un detalle de cada contacto seleccionado a la izquierda.

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:

Vista - Jinja2 - Seleccionar por id

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:

  1. Crear un bucle for similar al anterior en index.html que compruebe mediante un condicional el número de index pasado como argumento con el diccionario Todos y muestre los datos del elemento en cuestión.
  2. Crear una url dinámica que nos permita pasar el numero de index del elemento a mostrar como argumento.
  3. 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í:

index.html – Con condicional para mostrar un elemento específico
<!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á:

Jinja2 - Filtrar elementos por indice

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

meme programación 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 inicio el diccionario Todos con 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á:

Rutas estáticas y dinámicas en flask

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í:

index.html – Con enlaces a ver dinámico
<!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:

 

URLS - Sistema base en flask

Modelo-Vista-Controlador-JSON-Diagrama-Python

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.

  1. Vamos a crearlo puramente en HTML sin librerías. Este form tendrá el método action apuntando a nuestra ruta /editar donde modificaremos el elemento.
  2. En el controlador debemos procesar los datos del formulario, por lo que añadiremos la ruta editar y en ella debemos añadir un condicional que compruebe si se trata de la solicitud GET o POST.
  3. 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.

  1. 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:
      1. form action{{url_for(‘editar’, index = index_ver)}}» method=»post»

  2. 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):
    1. <input type=»hidden» name=»id» value=»{{Todos[index_ver].id}}«>

  3. En los input debemos rellenar con el dato existente por defecto y brindar la posibilidad de modificar el valor..
    1. <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:

 

Jinja2 - Modificar datos formularios en flask

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>

 

RUTAS - URLS - Sistema base de nuestro proyecto

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:

Diagrama-Solicitudes-get-post-jinja2-flask-python

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:

controlador.py – Ruta editar con procesamiento POST
@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.form es 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, Telefono y Direccion. 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 el id dado. 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ámetro index que 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 id que 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:

  1. Crearemos una nueva plantilla HTML (agregar_contacto.html) con un formulario vacío.
  2. 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).
  3. Modificaremos el botón «Agregar» en index.html para 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:

agregar_contacto.html – Código completo
<!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 Persona con 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í:

  1. El usuario hace clic en «Agregar» desde cualquier vista.
  2. Se muestra el formulario vacío (GET).
  3. El usuario completa los campos y envía (POST).
  4. El controlador procesa los datos, crea el contacto y lo guarda en el JSON.
  5. 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:

jinja2-formulario-agregar-flask-python

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:

  1. Añadir un enlace en el botón «Eliminar» de index.html que apunte a la ruta /eliminar/<id>.
  2. Crear una ruta en el controlador que reciba el id, llame al método eliminar_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:

Con esto, completamos las cuatro operaciones del CRUD:

  • CREATEagregar
  • READver e inicio
  • UPDATEeditar
  • DELETEeliminar

¡Nuestra primera aplicación Flask con CRUD funcional está terminada!

 
 

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

 

Código completo del Proyecto + Repositorio
Información de la aplicación

Directorio Telefónico – Aplicación CRUD con Flask y Jinja2

Esta es una aplicación web desarrollada con Flask (microframework de Python) que implementa un CRUD completo (Crear, Leer, Actualizar y Eliminar) sobre una base de datos JSON. El objetivo principal es mostrar cómo estructurar un proyecto siguiendo el patrón MVC (Modelo-Vista-Controlador) y cómo utilizar el motor de plantillas Jinja2 para generar contenido dinámico en el frontend.

Tecnologías utilizadas:

  • Python 3 – Lenguaje de programación.
  • Flask – Framework web ligero.
  • Jinja2 – Motor de plantillas para renderizar HTML dinámico.
  • JSON – Almacenamiento persistente de datos (archivo base_de_datos.json).

Funcionalidades CRUD:

  • CREATE – Añadir nuevos contactos mediante el formulario en /agregar.
  • READ – Listar todos los contactos en la página de inicio y ver detalles individuales en /ver/<id>.
  • UPDATE – Editar contactos existentes desde /editar/<id>.
  • DELETE – Eliminar contactos mediante la ruta /eliminar/<id>.

Estructura del proyecto:

  • models.py – Contiene la clase Persona y los métodos CRUD.
  • controlador.py – Define las rutas y la lógica de navegación.
  • templates/ – Archivos HTML (vistas) con sintaxis Jinja2.
  • DB/ – Carpeta que almacena el archivo JSON con los datos.

Propósito educativo:
Este proyecto está diseñado para enseñar los fundamentos del desarrollo web con Flask, la integración de Jinja2 y la implementación de operaciones CRUD sin necesidad de una base de datos relacional. Es un punto de partida ideal para quienes desean aprender a crear aplicaciones web en Python.

 

controlador.py
# ============================================================
# controlador.py - Aplicación Flask con CRUD sobre JSON
# ============================================================
# Este archivo actúa como el CONTROLADOR en el patrón MVC.
# Gestiona las peticiones HTTP, orquesta la lógica de negocio
# (usando el modelo Persona) y renderiza las vistas (templates).
# ============================================================

from flask import Flask, redirect, request, render_template
from models import Persona  # Importamos el modelo para acceder a los datos

app = Flask(__name__)  # Instancia de la aplicación Flask


# ============================================================
# RUTA PRINCIPAL (READ - Listar todos los contactos)
# ============================================================
@app.route('/', methods=['GET'])
def inicio():
    """
    Página de inicio de la aplicación.
    - Método: GET
    - Función: Obtiene todas las personas almacenadas en la base de datos JSON
      mediante el método leer_contacto('id', 'all') del modelo.
    - Renderiza: index.html pasando el diccionario 'Todos' con todas las personas
      y 'index_ver=0' para que por defecto se muestre la primera persona.
    """
    Todos = Persona.leer_contacto('id', 'all')
    return render_template('/index.html', Todos=Todos, index_ver=0)


# ============================================================
# RUTA VER (READ - Mostrar un contacto específico)
# ============================================================
@app.route('/ver/<int:index>', methods=['GET'])
def ver(index):
    """
    Muestra los detalles completos de una persona en particular.
    - Método: GET
    - Parámetro URL: index (entero) → posición de la persona en el diccionario.
    - Función: Recarga la misma vista index.html pero con 'index_ver' igual al
      índice recibido, lo que activa el condicional en la plantilla para mostrar
      solo esa persona.
    """
    Todos = Persona.leer_contacto('id', 'all')
    return render_template('/index.html', Todos=Todos, index_ver=index)


# ============================================================
# RUTA EDITAR (UPDATE - Modificar un contacto)
# ============================================================
@app.route('/editar/<int:index>', methods=['GET', 'POST'])
def editar(index):
    """
    Permite modificar los datos de una persona existente.
    - Métodos: GET y POST
    - Parámetro URL: index (entero) → posición de la persona a editar.
    - GET: Muestra el formulario con los datos actuales de la persona.
    - POST: Recibe los datos del formulario, actualiza el registro en la base de
      datos JSON mediante el modelo, y redirige a la misma vista para reflejar
      los cambios.
    - NOTA: Se actualiza cada atributo por separado usando el método
      actualizar_contacto() del modelo.
    """
    Todos = Persona.leer_contacto('id', 'all')
    if request.method == 'POST':
        # Obtenemos los datos enviados desde el formulario HTML
        id = int(request.form['id'])          # ID oculto de la persona
        Nombre = request.form['Nombre']
        Apellido = request.form['Apellido']
        Apodo = request.form['Apodo']
        Telefono = request.form['Telefono']
        Direccion = request.form['Direccion']

        # Llamamos al modelo para actualizar cada atributo
        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)
        # Podríamos redirigir a la misma página de edición para mostrar los cambios
        # return redirect(f'/editar/{index}')
    else:
        # Si es GET, solo mostramos el formulario sin procesar nada
        pass

    return render_template('/editar_contacto.html', Todos=Todos, index_ver=index)


# ============================================================
# RUTA AGREGAR (CREATE - Crear un nuevo contacto)
# ============================================================
@app.route('/agregar', methods=['GET', 'POST'])
def agregar():
    """
    Permite añadir una nueva persona a la base de datos.
    - Métodos: GET y POST
    - GET: Muestra un formulario vacío para que el usuario ingrese los datos.
    - POST: Recibe los datos del formulario, crea una nueva instancia de Persona
      con esos valores y llama a crear_contacto() para guardarla en el JSON.
      Luego redirige a la misma URL (GET) para recargar la página y mostrar la
      lista actualizada con el nuevo contacto.
    - El uso de redirect evita el reenvío del formulario al actualizar la página.
    """
    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 un objeto Persona y lo persistimos
        nuevo_contacto = Persona(Nombre, Apellido, Apodo, Telefono, Direccion)
        nuevo_contacto.crear_contacto()

        # Redirigimos a la misma página para que se recargue con la lista actualizada
        return redirect('agregar')
    else:
        pass

    return render_template('/agregar_contacto.html', Todos=Todos)


# ============================================================
# RUTA ELIMINAR (DELETE - Borrar un contacto)
# ============================================================
@app.route('/eliminar/<int:id>', methods=['GET'])
def eliminar(id):
    """
    Elimina una persona de la base de datos mediante su ID.
    - Método: GET (por simplicidad, aunque lo correcto sería DELETE)
    - Parámetro URL: id (entero) → identificador único de la persona.
    - Función: Llama al método eliminar_contacto(id) del modelo y redirige a la
      página principal para que se actualice la lista de contactos.
    - NOTA: En una aplicación profesional, se recomienda usar el método HTTP
      DELETE y una petición AJAX para mayor seguridad y buenas prácticas REST.
    """
    Persona.eliminar_contacto(id)
    return redirect('/')  # Redirige a la página de inicio


# ============================================================
# RUTA DE AGRADECIMIENTO (Ejemplo adicional)
# ============================================================
@app.route('/agradecimiento', methods=['GET'])
def agradecer():
    """
    Ruta de ejemplo que devuelve un mensaje de agradecimiento.
    - Método: GET
    - Función: Retorna un texto simple. Puede usarse como página de
      confirmación después de una acción.
    """
    return 'Gracias pythones.net!'


# ============================================================
# INICIO DE LA APLICACIÓN (Solo si se ejecuta directamente)
# ============================================================
if __name__ == '__main__':
    # Arrancamos el servidor de desarrollo de Flask
    # - host='127.0.0.1' → solo accesible desde la misma máquina
    # - port=5000 → puerto estándar de Flask
    # - debug=True → recarga automática al guardar cambios y muestra errores detallados
    app.run('127.0.0.1', 5000, debug=True)

 

models.py
# ============================================================
# models.py - Modelo de datos para la aplicación Flask
# ============================================================
# Este archivo actúa como el MODELO en el patrón MVC.
# Contiene la lógica de negocio y el acceso a la base de datos
# (en este caso, un archivo JSON que funciona como almacenamiento persistente).
# ============================================================

import os
import json

# ============================================================
# CONFIGURACIÓN DE LA RUTA DEL ARCHIVO JSON
# ============================================================

# Obtenemos la ruta absoluta del directorio donde se encuentra este script
THIS_FOLDER = os.path.dirname(os.path.abspath(__file__))
# Construimos la ruta al archivo base_de_datos.json dentro de la carpeta /DB/
my_file = os.path.join(THIS_FOLDER + '/DB/' 'base_de_datos.json')


# ============================================================
# FUNCIONES DE ACCESO A DATOS (Persistencia)
# ============================================================

def leer_json():
    """
    Lee el archivo JSON y devuelve su contenido como un diccionario de Python.
    - Abre el archivo en modo lectura ('r').
    - Carga los datos con json.load().
    - Cierra el archivo automáticamente.
    - Retorna el diccionario 'datos'.
    """
    with open(my_file, "r") as f:
        datos = json.load(f)
        f.close()
        return datos

# Cargamos los datos al iniciar el módulo (se ejecuta una sola vez)
datos = leer_json()


def modificar_json():
    """
    Guarda el diccionario 'datos' actualizado en el archivo JSON.
    - Abre el archivo en modo escritura ('w').
    - Escribe los datos con formato indentado (4 espacios) para legibilidad.
    - Cierra el archivo.
    - Esta función se llama después de cada operación que modifica los datos.
    """
    with open(my_file, "w") as modid:
        json.dump(datos, modid, indent=4)
        modid.close()


# ============================================================
# CLASE PERSONA - REPRESENTA UNA ENTIDAD DEL DOMINIO
# ============================================================

class Persona(object):
    """
    Clase que modela a una persona (contacto) en el directorio telefónico.
    - Atributos: id, nombre, apellido, apodo, telefono, direccion.
    - Proporciona métodos CRUD para gestionar instancias en la base de datos JSON.
    - El 'id' es autoincremental y se gestiona mediante 'contador_id'.
    """

    # Variable de clase que almacena el último ID asignado (se sincroniza con el JSON)
    contador_id = datos["Configuraciones"][0]["contador_id_db"]

    def __init__(self, nombre, apellido, apodo, telefono, direccion):
        """
        Constructor de la clase Persona.
        - Asigna un ID único tomado de contador_id.
        - Almacena los demás atributos proporcionados.
        - Imprime en consola el ID asignado (útil para depuración).
        """
        print("El contador está en: ", Persona.contador_id)
        self.id = Persona.contador_id          # ID único autoincremental
        self.nombre = nombre
        self.apellido = apellido
        self.apodo = apodo
        self.telefono = telefono
        self.direccion = direccion

    # ============================================================
    # MÉTODO CREATE - Crear un nuevo contacto
    # ============================================================

    def crear_contacto(self):
        """
        Guarda la instancia actual como un nuevo registro en la base de datos JSON.
        - Incrementa el contador de IDs en el archivo JSON y en la variable de clase.
        - Convierte la instancia a diccionario (__dict__) y la añade a la lista 'Personas'.
        - Persiste los cambios llamando a modificar_json().
        - Imprime el nuevo valor del contador (para depuración).
        """
        # Actualizamos el contador en la base de datos
        datos["Configuraciones"][0]["contador_id_db"] += 1
        modificar_json()
        print("El contador está ahora en: ", datos["Configuraciones"][0]["contador_id_db"])
        Persona.contador_id += 1  # Sincronizamos la variable de clase

        # Creamos una nueva instancia (esto es necesario para que __init__ asigne el ID correcto)
        # Pero aquí reutilizamos la instancia actual: convertimos self a diccionario
        nueva_persona = Persona(
            self.nombre,
            self.apellido,
            self.apodo,
            self.telefono,
            self.direccion
        ).__dict__

        # Añadimos el diccionario a la lista de personas
        datos['Personas'].append(nueva_persona)
        modificar_json()  # Guardamos los cambios

    # ============================================================
    # MÉTODO READ - Leer contactos (búsqueda)
    # ============================================================

    def leer_contacto(atr, valor):
        """
        Busca personas según un atributo y un valor dado.
        - Si valor == 'all': devuelve todas las personas con su índice como clave.
        - Si valor != 'all': busca personas donde el atributo 'atr' sea igual a 'valor'.
        - Retorna un diccionario {índice: persona_diccionario} con los resultados.
        - Este método es de clase (@staticmethod de facto) porque no necesita una instancia.
        """
        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:
            # Búsqueda por atributo específico
            encontradas = {}
            for persona in datos["Personas"]:
                if persona[atr] == valor:
                    indice = datos["Personas"].index(persona)
                    encontradas[indice] = persona
            return encontradas

    # ============================================================
    # MÉTODO UPDATE - Actualizar un contacto existente
    # ============================================================

    def actualizar_contacto(id, atr, nuevo_valor):
        """
        Actualiza un atributo específico de una persona identificada por su ID.
        - Itera sobre la lista de personas hasta encontrar el ID.
        - Una vez encontrado, obtiene su índice y modifica el atributo indicado.
        - Persiste los cambios llamando a modificar_json().
        - No retorna nada (la actualización se hace in-place).
        """
        for persona in datos["Personas"]:
            if persona["id"] == id:
                print(persona)  # Mostramos el estado anterior (depuración)
                indice = datos["Personas"].index(persona)
                datos["Personas"][indice][atr] = nuevo_valor
                print(datos["Personas"][indice][atr])  # Mostramos el nuevo valor (depuración)
                modificar_json()
                break  # Salimos del bucle tras encontrar y actualizar

    # ============================================================
    # MÉTODO DELETE - Eliminar un contacto
    # ============================================================

    def eliminar_contacto(id):
        """
        Elimina la persona con el ID especificado de la base de datos.
        - Itera sobre la lista de personas hasta encontrar el ID.
        - Una vez encontrado, obtiene su índice y lo elimina con pop().
        - Persiste los cambios llamando a modificar_json().
        - Muestra un mensaje en consola con la persona eliminada (depuración).
        """
        for persona in datos["Personas"]:
            if persona["id"] == id:
                print("Se va a borrar: ", persona)
                indice = datos["Personas"].index(persona)
                datos["Personas"].pop(indice)
                modificar_json()
                break  # Salimos tras eliminar

 

index.html
<!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>
          <td><a href="{{url_for('eliminar', id = persona.id)}}"><button name="eliminar">Eliminar</button></a></td>
        </tr>
        {% endfor %}
        <tr>
          <td><a href="{{url_for('agregar')}}"><button name="agregar">Agregar</button></a></td>
        </tr>
      </table>
    </div>
  </div>

</body>

</html>

 

editar_contacto.html
<!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>

 

agregar_contacto.html
<!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>

 

 

Repositorio GitHub

 

🎯 ¡Sígueme para no perderte nada!

meme programación

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

¡Compartir es una forma de agradecer! :)

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio