lunes, 12 de octubre de 2020

Flask 19. Usar Flask Blueprint para estructurar aplicaciones. Parte 2ª..

 



Usar Flask Blueprint para estructurar aplicaciones. Parte 2ª.


Siguiendo con el punto anterior, se dice que hay dos formas de organizar un proyecto, mediante una división Estructural o bien mediante una división Funcional.

División Estructural.


En una división Estructural, el código se agrupa basandonos en lo que hace dentro del programa. Es decir, se agrupan por ejemplo los archivos de vistas, de un lado, las plantillas por otro, el contenido estático, los formularios y así sucesivamente.

La estructura típica de esta división sería la siguiente:


+ directorio_de_trabajo/
   |_ app/
      |_ __init__.py
      |_ static/
      |_ templates/
         |_ public/
            |_ index.html
            |_ ...
         |_ users/
            |_ login.html
            |_ sign_up.html
            |_ ...
         |_ private/
            |_ index.html
         |_ ...
      |_ rutas/
         |_ __init__.py
         |_ private.py
         |_ public.py
         |_ users.py
         |_ ...
      |_ modelos/
         |_ users.py
         |_ ...
      |_ formularios/
         |_ users.py
         |_ ...
   |_ inicio.py
   |_ requirements.txt
   |_ ...


División Funcional.


Por el contrario en una división funcional, los diferentes componentes del código se van a agrupar en base a lo que hacen o para lo que se utilizan en la aplicación. Así todas las vistas, plantillas, modelos, formularios etc de una parte que tienen una relación común en la aplicación se agrupan dentro de un mismo paquete.

La estructura típica de esta división sería la siguiente:

+ directorio_de_trabajo/
   |_ app/
      |_ __init__.py
      |_ publico/
         |_ __init__.py
         |_ routes.py
         |_ static/
         |_ templates/
         |_ models.py
         |_ forms.py
         |_ ...
      |_ privado/
         |_ __init__.py
         |_ routes.py
         |_ static/
         |_ templates/
         |_ models.py
         |_ forms.py
         |_ ...
      |_ usuarios/
         |_ __init__.py
         |_ routes.py
         |_ static/
         |_ templates/
         |_ models.py
         |_ forms.py
         |_ ...
      |_ ...
      |_ static/
      |_ templates/
   |_ inicio.py
   |_ requirements.txt
   |_ ...


Para ver como aplicar esto que hemos visto, vamos a utilizar el código del capítulo 15 "Login de Usuarios" que puedes encontrar en esta dirección del repositorio de github. Utilizaremos en él un modelo funcional, aunque realmente el diseño del proyecto  puedes hacerlo tu como quieras, de la forma que se adapte a tus necesidades.

Al utilizar una división funcional, agruparemos los diferentes elementos en tres bloques. El bloque principal será el que tiene que ver con las funciones de autentificación del usuario (login, registro y log-out), otro bloque contendrá las páginas que son públicas (en el ejemplo solo la página inicial) y un último bloque con la página a a la que se accede una vez el usuario se ha logeado (dashboard).


Bloque de páginas públicas (En nuestro caso solamente la pagina_inicial)


Si abrimos el proyecto del capítulo 15, verás que tiene la siguiente estructura de directorios:



Empecemos a trabajar creando un nuevo directorio, dentro del directorio de trabajo y al nivel de la aplicación principal. Este archivo lo puedes llamar como quieras, yo lo llamaré app y contendrá todo el contenido de la aplicación.

Vamos a ir paso a paso. Dentro de este directorio, app, crearemos un archivo vacío llamado __init__.py para que python considere este directorio como un paquete y podamos importar diferentes recursos de ella. 

Lo más sencillo es empezar creando el blueprint para la parte pública de la aplicación. Recordemos que esta parte pública contendrá, en nuestro caso, la vista y la plantilla de la pagina_inicial o de bienvenida que es la que puede ver todo el mundo. 

Dentro del directorio app, creamos también el directorio public donde meteremos todo lo que hemos dicho en el párrafo anterior. Aquí dentro también creamos un nuevo archivo __init__.py

Para usar un blueprint siempre seguiremos los mismos pasos:

"Primero creación e inicialización del blueprint y segundo registro del mismo en la aplicación principal, en nuestro caso inicio.py".


En este último __init__.py que hemos creado, el que esta dentro del directorio public, tecleamos:

from flask import Blueprint

public = Blueprint('public', __name__, template_folder='templates')

from . import routes

 Esto ya lo habíamos visto lo que significaba en el capítulo anterior. Lo único nuevo es el "from . import routes". Con esto, lo que le decimos al programa es que de este mismo directorio, en donde esta el __init__.py, importe el módulo routes, que vamos a crear a continuación. Pero antes de esto creamos dentro del directorio public, la carpeta templates y cortamos y pegamos la plantilla pagina_inicial.html, que está en el directorio templates, pero en el primer nivel del directorio de trabajo.

Ahora si, creamos el archivo routes.py que contendrá la ruta de la vista de esta página_inicial.

routes.py

from flask import render_template
from . import public

@public.route('/')
def home():
    return render_template('pagina_inicial.html')

Comentémoslo un poco.

En la primera línea importamos el método render_template para poder renderizar la plantilla página_inicil.html. Luego le decimos que importe el objeto blueprint public que está en el mismo directorio y que creamos en el archivo __init__.py. Para finalizar con una función que define la vista de la manera habitual. 

Sin embargo esto aún no funcionaría porque nos falta un último paso, que es registrar el blueprint en la aplicación principal. Para ello en el archivo inicio.py antes de declarar las vistas tecleamos:

# importamos y registramos los blueprint
from app.public import public
app.register_blueprint(public)

Le estamos diciendo que importe el blueprint public y lo registre en la aplicación principal de Flask que es "app".

Ahora cada vez que vayamos a la url 127.0.0.1:5000 se utilizará este blueprint para ir a la página principal. 

IMPORTANTE:

Date cuenta que esta vista ya estaba definida en el archivo inicio.py con lo que tienes que buscar y borrar su código en este archivo, en inicio.py. Si no lo haces tendrías una función "/" en inicio.py y otra función "/" en /app/public/routes.py 

Para que quede un poco más claro la estructura de la aplicación hasta el momento quedará de la siguiente forma:

Si ahora pruebas el programa se renderizará la página principal correctamente, pero si intentas ir a otras opciones nos dará un error, puesto que ahora todo lo que hace referencia a el archivo pagina_inicial.html no está en el directorio templates principal, sino que se encuentra ahora en app/public/templates, ha cambiado la dirección a la que apuntan. Esto lo vamos a ir arreglando sobre la marcha según vayamos cambiando el resto de las plantillas.


Bloque de páginas privadas (a las que se puede acceder una vez logeados)


Por seguir con cosas fáciles, vamos ahora a preparar la parte a la que se podía acceder una vez que el usuario se había logeado en la aplicación. Estrictamente hablando, en esta parte ni siquiera se renderizaba una plantilla, tan solo la vista devolvía un mensaje por pantalla en el que se mostraba el siguiente texto "Bienvenido {}, esta es la página de control a la que se puede acceder si estas registrado"

Creamos un directorio llamado private dentro del directorio app.

directorio_trabajo/app/private

Seguimos los mismos pasos que en el punto anterior y creamos el archivo __init__.py en donde crearemos el blueprint "private" y cargaremos las rutas del mismo.

from flask import Blueprint
private = Blueprint("private",__name__)
# como no tiene de momento plantillas no usamos template_folder
from . import routes

A continuación, al mismo nivel creamos el archivo routes.py

directorio_trabajo/app/private/routes.py


from . import private
from flask_login import login_required, current_user

@private.route('/dashboard/')
# decorador para que no se pueda entrar en la vista si no estas logeado
@login_required
def dashboard():
    return '''<h2>Bienvenido {},esta es la pagina de control <br/> a la que se puede acceder 
    si estas registrado.</h2>
    <br />
    <a href="/">Volver</a> 
    '''.format(current_user.nombre_usuario)

Importamos el blueprint "private" y los módulos login_required y current_user de flask_login.

Hacemos un corta y pega de la vista /dashboard/ del archivo inicio.py en este archivo routes.py.

En el definíamos la función que va a definir la vista de la página privada (dashboard). Como es una página simple no se renderiza un archivo html sino que retornamos directamente un texto en formato html. Al tratarse de un ejemplo sencillo esta parte del código no tiene más vistas y por tanto es la única a la que se puede acceder cuando el usuario se haya logeado.

Para finalizar registramos el blueprint en la aplicación principal.



Bloque relacionado con las funciones de autentificación de usuarios.


Nos pondremos ahora con la parte de la aplicación que cuya función es registrar a los usuarios, logearlos y posteriormente realizar la desconexión. El proceso es el mismo que en puntos anteriores pero con algunas modificaciones que tenemos que realizar en el tema de las plantillas.

Comenzamos creando el directorio que contendrá todos los archivos y el código. Yo lo voy a llamar "auth" y estará en el directorio:

Directorio_de_trabajo/app/auth

y dentro creamos el archivo __init__.py donde definiremos el blueprint.

Directorio_de_trabajo/app/auth/__init__.py

from flask import Blueprint
auth = Blueprint('auth', __name__, template_folder="templates")
from . import routes
El código es igual que los anteriores. 

Comenzamos importando el método Blueprint de Flask (auth). 

Definimos el objeto blueprint "auth" que tendrá una carpeta al mismo nivel para almacenar las plantillas de sus vistas que llamaremos "templates". Esta carpeta es propia de este directorio y solo la van a usar las plantillas del grupo auth. 

Aunque todavía no hemos creado el archivo routes.py, que contendrá las rutas de las diferentes vistas, lo importamos para finalizar este archivo.

Creamos la carpeta "templates" en:

Directorio_de_trabajo/app/auth/templates

Para a continuación cortar y pegar todas las plantillas que estaban en:

Directorio_de_trabajo/templates a este nuevo directorio.

(login.html, signup.html y flask.html). Luego tenemos que hacerlas unas pequeñas modificaciones.

Seguimos registrando el blueprint en la aplicación principal inicio.py:



Ahora, creamos al mismo nivel el archivo routes.py que contendrá las vistas de este grupo.

Punto primero, importamos el blueprint:

 1. from . import auth

Seguimos trasladando de inicio.py todo lo relacionado con el inicio de sesión en el sistema de los usuarios, que era la vista 'login'.

Como esta parte es la más laboriosa, que no complicada, vamos a y ir explicándola muy despacio.

Nos vamos a inicio.py y cortamos y pegamos todo el código de la vista login en el archivo routes.py
Date cuenta de que en el código el decorador para la vista login no es @app.route(...) sino que como está en el blueprint auth tiene que ser @auth.route(...)

from . import auth

# cortado y pagado de la vista login desde inicio
@auth.route('/login/', methods=['GET', 'POST'])
def login():
    form = Formulario_de_Login()
    # (2) Si el usuario ya estuviera registrado no tiene sentido que volviera a hacer un login
    # asi que lo mandamos de vuelta a la página del dashboard
    por lo anterior
...
HASTA
...

        # Si el usuario no existe o la contraseña es incorrecta
        # return "<h2>Usuario o Contraseña incorrecta</h2>"
        flash('El usuario no existe o la contraseña es incorrecta')
        return redirect(url_for('login'))
    else:
        return render_template('login.html', form=form)

Si tal como esta el proyecto ahora mismo lo ejecutamos, nos dará el siguiente error, nada más cargar la página principal.


Esto se debe a que al intentar renderizar la página principal, los url_for que hacen referencia a esta vista se están buscando en la aplicación principal "app". Sin embargo no debe buscarlos ahí pues hemos movido la definición de esta vista al blueprint "auth". Afortunadamente el mismo programa nos da la solución, que es tan sencilla como en la plantilla que se carga al inicio, pagina_inicial.html, indicar en todos url_for los blueprint a los que corresponden. En esta primera vista es sustituir "login" por "auth.login"

Para ir dejándolo todo arreglado también modificaremos los enlaces para signup y logout, como puedes ver en la imagen inferior.



Al haber desplazado archivos se nos siguen produciendo errores. Si ejecutáramos en el programa la vista de login nos daría este error:


Esto se debe a que la vista esta usando un objeto que aun no hemos especificado  en este archivo y tampoco hemos movido el método que lo genera. En otras palabras, tenemos que cortar y pegar el archivo form.py que está en el directorio principal al directorio "auth", además de importar el objeto en el archivo routes.py.

 1 from . import auth
 2 from .form import Formulario_de_Login

Lo mismo nos va a ocurrir con unas cuantas cosas que estaban definidas o cuyo código estaba en inicio.py y que ahora tenemos que trasladar a routes.py para que funcionen sus vistas. Para no extendernos demasiado voy a poner el código de todo lo que hay que trasladar:

routes.py

from . import auth
from .form import Formulario_de_Login, Formulario_de_Registro
from flask import redirect, url_for, request, render_template, flash
# Por temas de seguridad en la ruta
from werkzeug.urls import url_parse
# para que la contraseña no se guarde como texto plano
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import current_user, login_user, logout_user, login_required
from inicio import db
from .modelos import Usuario
 

El último punto de este código hace referencia a la importación del objeto Usuario que va a estar dentro del archivo que vamos a crear a continuación en este mismo directorio, modelos.py. Contendrá la parte del código que gestionaba o mejor dicho definía la estructura del objeto usuario. En el se gestionaba la estructura de la base de datos donde guardamos el nombre, correo electrónico y contraseña de los usuarios que se registraban en nuestra aplicación.

Lo único que tenemos que hacer es crear el archivo modelos.py y cortar y pegar el código siguiente que hasta ahora se encontraba en nuestra aplicación principal, inicio.py.

modelos.py

from flask_login import UserMixin
from inicio import db

# --------------------------------- Modelo de bases de datos------------------------

# Hay que modificar la clase usuario para trabajar con la clase UserMixin de Flask-login


class Usuario(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    nombre_usuario = db.Column(db.String(15), unique=True)
    correo_electronico = db.Column(db.String(50), unique=True)
    contrasena = db.Column(db.String(80))


# ----------------------------------------------------------------------------------

Importante: este objeto Usuario es utilizado por en el cargador de Usuarios en Flask Login. Al haber desplazado el código de la aplicación principal a app/auth/modelos.py tenemos que importar el objeto Usuario previamente añadiendo lo siguiente al código en inicio.py:



Además, como dijimos anteriormente, tenemos que modificar los url_for que hay en la plantilla login.html para que hagan referencia a donde se encuentra ahora, después de moverlas, la vista a la que hacen referencia:

login.html



"Esto es muy importante, hemos de revisar cualquier punto donde hayamos utilizado URL_FOR ya sea plantilla o archivo de python, como por ejemplo routes.py para que señale al blueprint donde se encuentra la vista a la que hace referencia".


Hacemos lo mismo para la vista signup. Cortamos y pegamos el código entero de la vista desde inicio.py hasta el directorio routes.py justo de bajo de la vista login que habíamos movido anteriormente.

Igual que antes, al desplazar el archivo desde la app principal al blueprint auth tenemos que modificar el decorador de la vista. También modificaremos todos los url_for que haya tanto en el código de la vista, como en la plantilla signup.html para que hagan referencia a los directorios donde se encuentran actualmente.

routes.py



Una vez realizado todo lo anterior realizamos exactamente los mismos pasos para la vista logout.

Para finalizar simplemente limpiamos el código en inicio.py que ya no se usa como por ejemplo métodos que existían al comenzar el capitulo pero que ahora ya no son necesarios al cortar y pegar el código a otros blueprint.

Al final ya tenemos un código manejable, funcional, que puede ser usado en otros programas o por varias personas que trabajen el proyecto de forma independiente.

Al terminal la estructura de directorios quedaría de la siguiente manera:


Archivo con los paquetes necesarios para ejecutar la aplicación.


A estas alturas del curso de Flask, ya hemos instalado bastantes paquetes dentro de nuestro entorno virtual. Si alguna vez tenemos que volver a ejecutar este entorno en otro máquina, de memoria, seguro que tendríamos problemas para recordar que paquetes instalamos, por lo que la práctica generalmente aceptada para evitar este problema es escribir un archivo llamado requirements.txt. Este archivo estará en la carpeta raíz de nuestro proyecto, e indicara todas las dependencias o paquetes que necesita el proyecto para funcionar, junto con sus versiones.

¿Y esto como se hace te preguntarás? Pues realmente es muy fácil. 

(miEntorno) $ pip freeze > requirements.txt

El comando pip freeze volcará automáticamente toda la información sobre paquetes instalados y versiones en el archivo requirements.txt. De esta forma, si queremos recrear este mismo entorno virtual en otra máquina, lo único que tenemos que hacer es escribir:

(miEntorno) $ pip install -r requirements.txt

Con esto se instalará todo lo necesario para que pueda funcionar nuestro programa.


Y con esto terminamos el capitulo de los blueprint. Puedes ver el código completo terminado en el siguiente enlace:

Código del capítulo en github.


Próximo episodio. Desplegar una aplicación en Flask con Gunicorn y Nginx en una Rapsberry Pi.