Microframework: Flask#

Logo Flask

Documentación básica para trabajar con el Microframework Flask

Configuraciones#

Creación del proyecto#

  • Crear un Directorio para el proyecto y acceder al mismo.

  • Crear un entorno virtual.

  • Instalar flask: pip install flask.

  • Dentro del directorio creamos un directorio llamado templates y un archivo llamado main.py

Archivo principal#

El archivo principal básico:

 1# importamos flask y creamos el objeto con flask:
 2from flask import Flask
 3app = Flask(__name__)
 4
 5# Con un decorador definimos la ruta raiz y dentro una función que devuelve un mensaje.
 6@app.route('/')
 7def hello_workd():
 8    return 'Hola Mundo!!!'
 9
10# creamos la condición para arrancar la aplicación flask:
11if __name__ == "__main__":
12    # le pasamos la ejecución de la app y por parámetros la ruta del host y si esta en modo depuración o no.
13    app.run(host='localhost', debug=True)

Nota

Al tener habilitado el flag debug en la función run nos dará un pin al arrancar el servidor, dicho pin se usará para abrir el depurador en el navegador cuando tengamos algún error de manera que se pulsará sobre el icono de la consola en cualquier línea y se introduce el pin.

Rutas#

Rutas principales (main.py)#

Las rutas se suelen añadir en el archivo principal del proyecto:

 1from flask import Flask
 2app = Flask(__name__)
 3
 4@app.route('/')
 5def hello_workd():
 6    return 'Hola Mundo!!!'
 7
 8# despues de la / creamos otra ruta:
 9@app.route('/otra_ruta')
10def hola():
11    return 'Acceso a otra ruta!'
12
13if __name__ == "__main__":
14    app.run(host='localhost', debug=True)

rutas con parámetros#

  • Las rutas con parámetros:

 1from flask import Flask
 2app = Flask(__name__)
 3
 4@app.route('/')
 5def hello_workd():
 6    return 'Hola Mundo!!!'
 7
 8# con los símbolos mayor-menor podemos capturar un parámetro por defecto tipo cadena:
 9@app.route('/nombre/<persona>')
10def nombre(persona):
11    return 'Te llamas {}'.format(persona)
12
13# Podemos definir que sea un entero o coma flotante:
14@app.route('/edad/<int:edad>')
15def edad(edad):
16    return 'Tienes: {} años'.format(edad)
17
18if __name__ == "__main__":
19    app.run(host='localhost', debug=True)

Rutas por uno o más métodos#

Es posible estipular uno o varios métodos permitidos en una ruta:

 1from flask import Flask
 2# importamos la librería request que funciona igual que en django:
 3from flask import request
 4
 5app = Flask(__name__)
 6
 7@app.route('/')
 8def hello_workd():
 9    return 'Hola Mundo!!!'
10
11# el decorador recibe una lista de métodos permitidos:
12@app.route('/metodos', methods=['GET', 'POST'])
13def login():
14    # preguntamos si el método es GET que nos abra sesion y si no que nos mande de vuelta al formulario:
15    if request.method == 'GET':
16        return 'Recibido ' + request.method
17    else:
18        print("Es otro método")
19
20if __name__ == "__main__":
21    app.run(host='localhost', debug=True)

Nota

aunque en este ejemplo se usa GET para validar el metodo de entrada, es más común y más cómodo validar el método POST, ya que este último podría presentar más iteracciones en el código que el método GET.

Redirecciones#

 1# cargar librerías redirect y url_for:
 2from flask import Flask, render_template, redirect, url_for
 3
 4
 5app = Flask(__name__)
 6
 7@app.route('/')
 8def hello_workd():
 9    # Redireccionar a otra ruta usando el nombre de su función:
10    return redirect(url_for('saludar'))
11
12
13@app.route('/saludar', methods=['GET'])
14def saludar(nombre=None):
15    return 'Hola desde otra ruta'
16
17
18if __name__ == "__main__":
19    app.run(host='localhost', debug=True)

Error 404#

Procedimiento común para redirección 404:

  • Crear en templates archivo error.html:

 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <title>ERROR 404</title>
 8</head>
 9<body>
10    <h1>ERROR 404</h1>
11    <p>La página solicitada no existe</p>
12</body>
13</html>
  • Crear error 404 cuando introducimos rutas incorrectas:

 1from flask import Flask, render_template, redirect, url_for
 2
 3app = Flask(__name__)
 4
 5@app.route('/')
 6def hello_workd():
 7    return redirect(url_for('saludar'))
 8
 9
10@app.route('/saludar', methods=['GET'])
11def saludar(nombre=None):
12    return 'Hola desde otra ruta'
13
14# cargamos la ruta de error cuando no se encuentre página:
15@app.errorhandler(404)
16def error(error): # este recibe un parámetro
17    return render_template('error.html'), 404
18
19if __name__ == "__main__":
20    app.run(host='localhost', debug=True)

Vistas#

Las vistas en Flask no suelen usarse, en su lugar se visualizan los templates con Jinja

Templates (Jinja2)#

Jinja2 es el motor de plantillas que se utiliza de serie en Flask.

  • En la carpeta templates se crea un archivo por ejemplo prueba.html:

 1<!DOCTYPE html>
 2<html lang="es">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6    <title>Prueba con Flask</title>
 7</head>
 8<body>
 9    <h1>Plantillas Jinja2</h1>
10    <p>primera toma de contacto con Flask</p>
11</body>
12</html>
  • De vuelta a main.py se renderiza el template:

 1# importamos render_template para gestionar plantillas:
 2from flask import Flask, render_template
 3
 4app = Flask(__name__)
 5
 6@app.route('/')
 7def hello_workd():
 8    return 'Hola Mundo!!!'
 9
10@app.route('/prueba')
11def prueba(nombre=None):
12    # Renderizamos el archivo prueba:
13    return render_template('prueba.html')
14
15if __name__ == "__main__":
16    app.run(host='localhost', debug=True)

Template Tags#

Los Template Tags son un tipo de etiquetas especiales en Jinja2 que se utilizan en las plantillas para ejecutar respuestas backend.

Estas etiquetas suelen tener dos tipos de estructuras: {% instrucción %} o {{ datos }} según el tipo de tarea que vayamos a ejecutar.

Template tag base#

Una buena práctica para no repetir código en plantillas es coger todo el contenido común y almacenarlo en una plantilla base:

  • En templates crear un archivo llamado base.html:

 1<!DOCTYPE html>
 2<html lang="es">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6    <title>Prueba con Flask</title>
 7</head>
 8<body>
 9    {% block cuerpo %}{% endblock %}
10</body>
11</html>
  • En el resto de archivos html se elimina todo lo que contemple fuera del body del siguiente modo:

 1{% extends 'base.html' %}
 2
 3{% block cuerpo %}
 4<form method="POST" action="/subir" enctype="multipart/form-data">
 5    <label for="documento">Subir archivo</label>
 6    <br>
 7    <input type="file" name="documento">
 8    <br><br>
 9    <input type="submit">
10</form>
11{% endblock %}

Template tag static#

  • En la raiz del proyecto crear carpeta llamada static.

  • Crear archivo style.css:

1body{
2    background: yellow;
3    color: green;
4}
  • En el template se vincula:

 1<!DOCTYPE html>
 2<html lang="es">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6    <title>Prueba con Flask</title>
 7    <!-- Para añadir un archivo css utilizamos url for en una etiqueta jinja2 -->
 8    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
 9</head>
10<body>
11    <h1>Plantillas Jinja2</h1>
12    <p>primera toma de contacto con Flask</p>
13</body>
14</html>

Template tag de datos#

Los template tags de datos muestran información que enviamos desde la vista al template.

  • Si nos vamos a views.py para añadir un dato:

 1from flask import Flask, render_template
 2
 3app = Flask(__name__)
 4
 5@app.route('/')
 6def hello_workd():
 7    return 'Hola!!!'
 8
 9
10@app.route('/hola')
11def saludar():
12    nombre = "Guillermo"
13    return render_template('hola.html', nombre=nombre)
14
15
16if __name__ == "__main__":
17    app.run(host='localhost', debug=True)
  • Ahora que tenemos un dato, podemos mostrarlo en cualquier template de nuestra app:

 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <title>Document</title>
 8</head>
 9<body>
10    <p>Hola {{nombre}}</p>
11</body>
12</html>

Template tag for#

  • Se crea un listado en main.py:

 1from flask import Flask, render_template
 2
 3app = Flask(__name__)
 4
 5@app.route('/')
 6def hello_workd():
 7    return 'Hola!!!'
 8
 9
10@app.route('/hola')
11def saludar():
12    personas = ["Guillermo", "Antonio", "Josefa", "Adrián"]
13    return render_template('hola.html', personas=personas)
14
15
16if __name__ == "__main__":
17    app.run(host='localhost', debug=True)
  • Y ahora podemos recorrer el diccionario en nuestro template con el template tag for:

 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <title>Document</title>
 8</head>
 9<body>
10    <ul>
11        {% for persona in personas %}
12            <li>{{ persona }}</li>
13        {% endfor %}
14    </ul>
15</body>
16</html>

Template tag if#

Con el template tag if podemos establecer condiciones dentro de los templates, retomando el ejemplo de for vamos a pintar de verde uno de los registros:

  • Template condicional que recibe un parámetro:

 1<!DOCTYPE html>
 2<html lang="es">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6    <title>Prueba con Flask</title>
 7</head>
 8<body>
 9    {% if nombre %}
10        <h1>Hola {{ nombre }}</h1>
11    {% else %}
12        <h1>Hola mundo</h1>
13    {% endif %}
14</body>
15</html>
  • Cargar el template y pasarle parámetro:

 1from flask import Flask, render_template
 2
 3app = Flask(__name__)
 4
 5@app.route('/')
 6def hello_workd():
 7    return 'Hola Mundo!!!'
 8
 9
10@app.route('/saludos/<nombre>')
11def saludos(nombre=None):
12    return render_template('saludos.html', nombre=nombre) # le pasamos el template y la variable con la clave nombre
13
14if __name__ == "__main__":
15    app.run(host='localhost', debug=True)

Formularios#

En Django podemos crear formularios individuales y reutilizables.

Formularios via POST#

  • Se crea el archivo por ejemplo form.html:

 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <title>Document</title>
 8</head>
 9<body>
10    <form method="POST">
11        <input type="text" name="nombre" placeholder="Tu nombre">
12        <input type="text" name="apellidos" placeholder="Tus apellidos">
13        <input type="submit" value="Saludar">
14    </form>
15</body>
16</html>
  • Lo siguiente es registrar las rutas en Flask:

 1from flask import Flask, render_template, redirect, url_for, request
 2
 3app = Flask(__name__)
 4
 5@app.route('/')
 6def hello_workd():
 7    return redirect(url_for('saludar'))
 8
 9
10@app.route('/saludar', methods=['GET'])
11def saludar():
12    return render_template('form.html')
13
14@app.route('/saludar', methods=['POST'])
15def saludo():
16    nombre = request.form['nombre']
17    apellidos = request.form['apellidos']
18    return 'Te llamas ' + nombre + " " + apellidos
19
20
21if __name__ == "__main__":
22    app.run(host='localhost', debug=True)

Recuperar archivos en Flask#

De este modo se gestionaría la subida de un archivo desde un formulario:

  • Crear en la raiz un directorio llamado subidas

  • Crear en templates un archivo llamado subidas.html:

 1<!DOCTYPE html>
 2<html lang="es">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6    <title>Prueba con Flask</title>
 7</head>
 8<body>
 9    <form method="POST" action="/subir" enctype="multipart/form-data">
10        <label for="documento">Subir archivo</label>
11        <br>
12        <input type="file" name="documento">
13        <br><br>
14        <input type="submit">
15    </form>
16</body>
17</html>
  • Por último registrar dos rutas en main.py:

 1from flask import Flask, render_template, request
 2# añadimos también una utilidad para generar nombres seguros de archivos:
 3from werkzeug.utils import secure_filename
 4
 5app = Flask(__name__)
 6
 7@app.route('/')
 8def hello_workd():
 9    return 'Hola Mundo!!!'
10
11# Ruta del form:
12@app.route('/subir', methods=['GET'])
13def hola(nombre=None):
14    return render_template('subidas.html')
15
16# Ruta para procesar la petición:
17@app.route('/subir', methods=['POST'])
18def subir():
19    if request.method == 'POST':
20        # recuperamos el archivo del parametro files:
21        f = request.files['documento']
22        # ahora guardamos el archivo con un nombre seguro:
23        f.save('./subidas/{}'.format(secure_filename(f.filename)))
24        return 'Se ha subido correctamente'
25
26if __name__ == "__main__":
27    app.run(host='localhost', debug=True)

Bases de datos con SqlAlchemy#

Instalar librería#

  • Para usar esta librería se instala: pip install Flask-Sqlalchemy

Crear paquete del proyecto#

Se crea un paquete llamado application y dentro un archivo __init__.py para inicializarlo, se mueve aquí el archivo principal de la aplicación.

Configurar base de datos#

En el paquete application del proyecto se crea un archivo config.py:

1import os
2
3secret_key = 'CAB7D2CD6CCDC28113C9A3189DEE647BD945B0903EFEC1E160F2ADB5203CE649'
4PWD = os.path.abspath(os.curdir)
5
6DEBUG = True # Modo depuración lo pasamos aquí
7# Vinculamos el conector a la base de datos, en este caso sqlite:
8SQLALCHEMY_DATABASE_URI = 'sqlite:///{}/dbase.db'.format(PWD)
9SQLALCHEMY_TRACK_MODIFICATIONS = False # por rendimiento definimos el track en false

ahora editamos el archivo principal para añadir los cambios:

 1from flask import Flask
 2# Se carga el sqlalchemy también aqui:
 3from flask_sqlalchemy import SQLAlchemy
 4# se carga el archivo de configuración:
 5from application import config
 6
 7app = Flask(__name__)
 8
 9# se añade una linea para cargar el archivo config en app:
10app.config.from_object(config)
11# y se carga la base de datos:
12db = SQLAlchemy(app)
13
14@app.route('/')
15def inicio():
16    return 'Página principal'
17
18if __name__ == "__main__":  # establecemos el debug que tenemos en config:
19    app.run(host='localhost', debug=config.DEBUG)

Crear modelo de datos#

En el paquete application del proyecto se creará un archivo llamado models.py:

 1# Se importan los tipos de datos:
 2from sqlalchemy import *
 3# se importa el relacionador:
 4from sqlalchemy.orm import relationship
 5# se importa la configuración de la base de datos:
 6from application.app import db
 7
 8
 9# creamos un primer modelo que será relacionable con el segundo:
10class Categories(db.Model):
11    """ Categorías """
12    # se define nombre interno de tabla y los campos:
13    __tablename__ = 'categories'
14    id = Column(Integer, primary_key=True)
15    name = Column(String(100))
16
17    # retornar clase y función:
18    def __repr__(self):
19        return (u'<{self.__class__.__name__}: {self.id}>'.format(self=self))
20
21
22# Crear otro modelo relacionado con el primero:
23class Articles(db.Model):
24    """ Artículos """
25    __tablename__ = 'articles'
26    id = Column(Integer, primary_key=True)
27    nombre = Column(String(100),nullable=False)
28    precio = Column(Float,default=0)
29    iva = Column(Integer,default=21)
30    descripcion = Column(String(255))
31    image = Column(String(255))
32    stock = Column(Integer,default=0)
33    CategoriaId=Column(Integer,ForeignKey('categories.id'), nullable=False)
34    categoria = relationship("Categories", backref="Articles")
35
36    def precio_final(self):
37        return self.precio*self.iva/100
38
39    def __repr__(self):
40        return (u'<{self.__class__.__name__}: {self.id}>'.format(self=self))

Crear base de datos#

Para crear la base de datos ejecutamos la línea de comandos python y los siguientes comandos:

  • importar configuración base de datos: from application.app import db

  • importar modelos de datos a crear: from application.models import Categories, Articles

  • Ejecutar creación de tablas: db.create_all()

Migrar datos modificados en el modelo#

Actualmente Flask no incorpora un modelo de migración de datos como en el caso de Django. Si se añade un nuevo dato hay que ejecutar primero db.drop_all() y a continuación db.create_all()

CRUD desde consola python#

  • importar configuración base de datos: from application.app import db

Crear registro#

  • asignar valor al modelo que se quiere adherir dato: cat=Categories(name="RPG")

  • Cargar en el orm los datos: db.session.add(cat)

  • Cargar en el orm varios datos: db.session.add_all([cat1, cat2])

  • Comitear los datos en la base de datos: db.session.commit()

Leer registro#

  • Cargar una lista completa de registros: cat=Categories.query.all() (se tiene que recorrer con un bucle)

  • Cargar primer elemento de la lista: cat=Categories.query.first()

  • Cargar un elemento de la lista por dato de referencia: cat=Categories.query.get(1)

  • Filtrar registros: Categories.query.filter_by(name="RPG").all()

  • Filtrar por múltiples campos: Categories.query.filter_by(name="RPG").filter_by(subname="rol").all()

  • Ortdenar registros recuperados: Categories.query.order_by("name").all()

Nota

Cada registro recuperado es un objeto, por lo que acceder a cada campo es igual al acceder a un atributo de una clase ej: cat.name

Nota

Al igual que se filtran registros y se obtienen todos los resultados posibles, podemos obtener con first() el primer resultado posible.

Editar registro#

  • Cargar un elemento de la lista por dato de referencia: cat=Categories.query.get(1)

  • Modificar un campo del elemento: cat.name="Rol"

  • Cargar en el orm los datos: db.session.add(cat)

  • Comitear los datos en la base de datos: db.session.commit()

Eliminar registro#

  • Cargar un elemento de la lista por dato de referencia: cat=Categories.query.get(1)

  • Cargar en el orm los datos con delete: db.session.delete(cat)

  • Comitear los datos en la base de datos: db.session.commit()

Trabajar con relaciones#

A partir de un campo foreign key se pueden obtener los datos de la otra tabla relacionada como un objeto: * Si recuperamos un registro de la tabla articles: art1=Articles.query.get(1) * Al estar relacionada con categories se puede recuperar la información de la categoría: art1.categories.nombre

Uso del ORM en archivos py#

El uso del orm es similar al visto en la consola, ejecutando las operaciones desde scripts python:

1from aplicacion.models import Articles
2@app.route('/')
3def inicio():
4    articulos=Articles.query.all()
5    return render_template("inicio.html",articulos=articulos)
  • Esto en una plantilla se usaría así:

1<div class="panel-heading">Videojuegos</div>
2    <table class="table">
3            {% for art in articulos %}
4                <tr>
5                    <td>{{art.nombre}}</td>
6                </tr>
7            {% endfor %}
8    </table>
9</div>

Sistema de usuarios y sesiones#

Modelo de usuarios#

Antes de nada hay que crear un modelo de usuarios en la base de datos:

 1class Usuarios(db.Model):
 2"""Usuarios"""
 3__tablename__ = 'usuarios'
 4id = Column(Integer, primary_key=True)
 5username = Column(String(100),nullable=False)
 6password_hash = Column(String(128),nullable=False)
 7nombre = Column(String(200),nullable=False)
 8email = Column(String(200),nullable=False)
 9admin = Column(Boolean, default=False)
10
11def __repr__(self):
12    return (u'<{self.__class__.__name__}: {self.id}>'.format(self=self))
13
14@property
15def password(self):
16    raise AttributeError('password is not a readable attribute')
17
18@password.setter
19def password(self, password):
20    self.password_hash = generate_password_hash(password)
21def verify_password(self, password):
22    return check_password_hash(self.password_hash, password)

…. TODOOOO