viernes, 21 de agosto de 2020

Flask 14. Usando una base de datos en Flask, Flask-SQLAlchemy

 

Base de datos azul icono vector de la imagen | Vectores de dominio ...


Usando una base de datos en Flask, Flask-SQLAlchemy


Anteriormente. Flask 13. Subiendo archivos al servidor con Flask-WTF.


https://flask-sqlalchemy.palletsprojects.com/en/2.x/

Flask- SQLALchemy es un ORM (Object Relational Mapper) o lo que es lo mismo, un módulo que nos permite interactuar con una base de datos SQL sin preocuparnos por la sintaxis de SQL, es decir vamos a poder crear, leer, actualizar y borrar datos (CRUD en inglés) simplemente utilizando métodos de python y no del lenguaje de programación de la base de datos en concreto. Esto nos va a permitir trabajar con objetos y no con tablas y SQL. 

Otra ventaja es que podemos utilizar diferentes bases de datos: sqlite3, mysql o postgreSql sin tener que modificar prácticamente nuestra aplicación. Esto nos permite, por ejemplo, que mientras creamos la aplicación estemos trabajando con sqlite3 y cuando terminemos el proyecto pasar a una base de datos MySql sin tener que prácticamente modificar el programa.


Instalación de Flask-SQLAlchemy


Si estas usando un entorno virtual, entra en él y teclea:

pip install Flask-SQLAlchemy

En todos los capítulos anteriores las variables que podíamos configurar en Flask (debug=True o secret_key, por ejemplo) estaban dentro de nuestra aplicación principal, inicio.py. A partir de este momento vamos a crear un archivo solamente para guardar todo lo que tengamos que configurar, lo que nos va a resultar mas cómodo.

Configuración de Inicio.py para usar FLask-SQLAlchemy


Todos los parámetros de configuración de la aplicación van a estar en un nuevo archivo. Lo puedes llamar como quieras, yo le llamaré "config.py". Estará dentro de nuestro directorio de trabajo al mismo nivel que inicio.py. Quedaría de la siguiente forma:

Código de configuración del archivo config.py. Se puede encontrar el código al fina del cápitulo
Código archivo config.py

  • SECRET_KEY: Los utilizaremos para cifrar los tokens para el mecanismo de seguridad csrf en los formularios.

  • PWD: es una variable que contendrá el directorio de trabajo de la aplicación. Previamente habíamos importado el modulo standar de python os. Ahora lo utilizamos para que nos devuelva la ruta absoluta de nuestro directorio de trabajo. Algo así en Linux como: '/home/usuario/Escritorio/Flask/Aplicacion'. Luego lo utilizaremos para indicar al programa, donde está nuestra base de datos.

  • DEBUG=True: para inicializar el depurador del servidor y poder hacer y ver cambios sobre la marcha.

  • SQLALCHEMY_DATABASE_URI: indicamos la dirección de la base de datos con la que se realizará la conexión o con la que vamos a trabajar. Su estructura dependerá del tipo de base de datos que vayamos a usar. En estos ejemplos vamos a usar sqlite3 con lo que utilizamos: 'sqlite:///{}/database/dbase.db'.format(PWD). Es decir, le decimos que busque la base de datos "dbase.db" en el directorio database. 
Pero no te preocupes, aun no tenemos nada creado, solo le hemos dicho donde encontrar la base de datos en el futuro, pero te repito, aun no tenemos nada creado. La estructura final quedaría:
    Directorio de trabajo / database / dbase.db 


    Ahora si, para que todo este más ordenado crea una carpeta llamada "database", que de momento dejarás vacía, y donde más adelante crearemos la base de datos con el nombre de dbase.db. Si utilizáramos otro tipo de base de datos, por ejemplo mysql, la estructura del URI sería algo así como: mysql://username:password@server/db


    Básicamente, este comando es el que variará dependiendo de si usamos sqlite3 u otro tipo de base de datos. Nada más. El resto del código permanecerá prácticamente igual. Esta es la ventaja de usar sqlalchemy.

    • SQLALCHEMY_TRACK_MODIFICATIONS: deshabilitamos la gestión de notificaciones de sqlalchemy.

    Este fichero, config.py,  lo utilizaremos en inicio.py para cargar las variables de configuración. ¿Cómo haremos esto? Pues importando el modulo config que acabamos de crear y utilizando la siguiente instrucción:

    app.config.from_object(config)

    Aunque también puedes no importar config, siempre que este al mismo nivel que inicio.py y usar:

    app.config.from_object('config')

    Lo mismo que antes pero con las comillas.

    En este capítulo optaré por la primera opción.

    Además en el programa principal vamos a crear un objeto que represente a nuestra base de datos.

    db = SQLAlchemy(app)


    Con todo lo anterior, el archivo inicio.py quedaría:



                                            from flask import Flask
                                            import config
                                            from flask_sqlalchemy import SQLAlchemy
                                            app = Flask(__name__)
                                            app.config.from_object(config)
                                            db = SQLAlchemy(app)


                                            @app.route('/')
                                            def home():
                                                return 'esto es la página de inicio'


                                            @app.errorhandler(404)
                                            def page_not_found(error):
                                                 return "pagina no encontrada", 404


                                            if __name__ == '__main__':
                                                app.run()



    Diseño y creación del modelo de base de datos.


    Para seguir avanzando en los conceptos de este tema vamos hacer una pequeña y sencilla aplicación. Imaginemos que tenemos una pequeña empresa y que necesitamos una aplicación para llevar el registro de nuestros empleados. De cada empleado vamos a registrar:
    • El nombre
    • Un correo electrónico
    • Un teléfono.

    Un inciso.

    En la parte de diseño para que no quede tan simple he utilizado bootswatch que es como bootstrap pero con plantillas ya prediseñadas de diversos colores. He escogido el tema Sketchy. Esta cargado en nuestra plantilla base antes de cualquier otro CSS. 

    Puedes hacerlo de dos formas:

    1.- Le das al botón Download y descargas el script en local. Luego pones donde esta el archivo en <link rel="stylesheet" href="ruta del archivo descargado"> 
    2.- Te pones encima del botón Download y con el botón derecho seleccionas "Copiar dirección de enlace" y la pegas dentro de <link rel="stylesheet" href="Pegar el enlace aquí">

    Para el degradado de fondo he utilizado uiGradients cargado en la etiqueta <body>.

    Continuamos. 

    Vamos a modelar la base de datos que vamos a necesitar, para que contenga los campos (id, nombre, correo y teléfono). En inicio.py ponemos el código justo después de crear el objeto db y antes de las  vistas. 

    Vamos a comentarlo:

                    15     class Empleados(db.Model):
                    16         '''Tabla de datos de los empleados de la empresa'''
                    17        __tablename__ = 'empleados'
                    18         id = db.Column(db.Integer, primary_key=True)
                    19         nombre = db.Column(db.String(50), unique=True, nullable=False)
                    20         correo = db.Column(db.String(30), unique=True, nullable=False)
                    21         telefono = db.Column(db.Integer)
                    22
                    23         def __repr__(self):
                    24             return f'<Empleado {self.nombre}>'


    En la línea 15 creamos una clase que heredara de db.Model y que llamaremos Empleados. Eso nos permitirá modelar la base de datos. Dentro de la clase definiremos todos los campos que están relacionados con los empleados: El id o referencia del empleado, el nombre, correo electrónico y teléfono.

    En la variable __tablename__ indicamos el nombre de la tabla a la que corresponde esta clase. Por defecto Flask-SQLAlchemy se referirá a nuestra tabla con el nombre de la clase en minúsculas y separando las palabras con un guion bajo. En nuestro caso, por tanto, daría igual ponerlo que no, pero he preferido hacerlo para que se vea como ejemplo. 

    Indicaremos los distintos campos que necesitaremos usando el constructor db.Column y dentro definimos los distintos tipos de datos que se pueden guardar. 

    Entre otros puedes usar los siguientes:

    - Boolean - para guardar tipos de dato de tipo Boleano. Como valores del parámetro admite None, True, False, 0 o 1.

    - DateTime - para guardar la fecha y hora devuelto por el modulo de python datetime.

    - Float - para guardar números de coma flotante.

    - Integer - para guardar números enteros.

    - String - para guardar cadenas de tipo string: texto, caracteres etc. Le puedes pasar como primer parámetro el número máximo de caracteres que acepta el campo "db.Column(db.String(max.caracteres)" o puedes conseguir lo mismo con un parámetro usando "length = max. caracteres."

    - Text - muy parecido al anterior. No se puede especificar el tamaño máximo del campo, tiene una longitud de string variable.

    Puedes indicar que el campo tenga un valor por defecto si no se indica otro. Por ejemplo un campo numérico que tenga un valor de 0 sería:

    db.Column(db.Integer, default=0)

    Además del tipo de datos podemos indicar entre otros los siguiente atributos de cada campo

    primary_key automáticamente se crea al generar un registro para diferenciar a cada registro. 
    - unique o campo único que no puede haber otro de igual contenido en la tabla. 
    - nullable - Si su Valor es False obliga a que el campo siempre tenga un valor, no puede ser nulo.
    - index - Si especificamos que el campo este indexado con index=True, esto acelerará y hará que sean más eficientes las búsquedas por ese campo.
     
    Al final utilizamos def __repr__(self) para obtener una representación legible del objeto Empleado.

    Estarás pensando...¿y donde o cuando hemos creado el archivo con la base de datos? Pues hasta ahora en ningún sitio. Solo hemos diseñado la clase que contendrá la base de datos y creado el directorio "database". Si recuerdas, en el archivo config.py le dijimos a la base de datos que esta se llamaría dbase.db y estará en el directorio database al mismo nivel que la aplicación principal. Esto lo hicimos con la instrucción:

    SQLALCHEMY_DATABASE_URI = 'sqlite:///{}/database/dbase.db'.format(PWD)

    Ha llegado el momento de crear la base de datos. Para ello tenemos que estar dentro del directorio de trabajo que contiene nuestros archivos y ejecutar el shell de python, importar db del modulo inicio y ejecutar db.create_all(). 

                        julian@ubuntu:~/Escritorio/Flask/14$ source ../miEntorno/bin/activate
                        (miEntorno) julian@ubuntu:~/Escritorio/nFlask/14$ python
                        Python 3.6.9 (default, Jul 17 2020, 12:50:27) 
                        [GCC 8.4.0] on linux
                        Type "help", "copyright", "credits" or "license" for more information.
                        >>> from inicio import db
                        >>> db.create_all()
                        >>> 
    Nota: si esta usando un entorno virtual entra primero dentro del mismo.

    IMPORTANTE:

    1º) Nuestra aplicación esta trabajando con SQlite, por lo que al ejecutar el código anterior, este detecta que la base de datos no existe aún y la crea. Sin embargo, cuando trabajes con servidores de bases de datos como MySQL y PostgreSQL, tendrás que crear tu la base de datos en el servidor de la base de datos.

    2º) Estás instrucciones no actualizan la estructura de la base de datos si cambiamos el modelo. Por ejemplo si a posteriori añades un campo más a los anteriores, por ejemplo un campo llamado "salario", tendrías que borrar las tablas y crearlas de nuevo usando las instrucciones:

    from inicio import db
    db.drop_all()
    db.create_all()

    Podemos utilizar también la migración de base de datos que te permite, al cambiar el modelo actualizar la estructura de la base de datos. Para trabajar con migraciones puedes usar la extensión Flask-Migrate.

    *** Aquí voy a hacer un paréntesis en este capítulo y explicar un poco como funciona esta extensión. Aunque en el código de esta página no lo voy a utilizarlo, me parecía interesante verlo para futuros proyectos. El texto del capítulo continua después de lo que está entre las dos líneas amarillas. Si no te interesa este paquete o simplemente quieres seguir con el capítulo sin interrupciones, puedes saltártelo.


    --------------------------------------------------------------------

    Como siempre, para utilizar flask-migrate, lo primero que tenemos que hacer es entrar en nuestro entorno virtual e instalarlo con:

    (venv) $ pip install flask-migrate
    luego si queremos utilizarlo tenemos que importarlo y declararlo en el archivo inicio.py de esta forma, una vez que hemos creado la estructura de la base de datos con la clase Empleado y hemos creado antes app y db:




    Bien como nuestra aplicación inicial, no se llama app.py ni wsgi.py, tenemos que decirle a Flask para que haga lo mostraré a continuación, cual es nuestra aplicación principal. Para ello solo tenemos que teclear lo siguiente:

    (venv) $ export FLASK_APP=inicio.py

    Esto solo dura mientras tengas la sesión abierta, si la cierras y vuelves a entrar tendrás que teclearlo de nuevo. Aprovecho esto para decirte que hasta ahora habíamos ejecutado el programa lanzándolo con el interprete de python tecledando:

    (venv) $ python inicio.py
    Pero si declaras cual es la aplicación principal en Flask también puedes lanzarlo con:

    (venv) $ flask run
    dicho lo cual volvemos al tema de la base de datos. La clase Empleados que hemos creado anteriormente define la estructura inicial de nuestra base de datos para esta aplicación. Pero cuando la aplicación siga creciendo es muy normal que haya cambios en la misma. Podemos añadir nuevos campos, añadir más tablas, borrar algún campo etc.

    Alembic (el framework de migración utilizado por Flask-Migrate) hará estos cambios en la estructura de la base de datos de forma que no tengamos que volver a crearla desde cero.  Pues bien, una vez que le hemos dicho a flask cual es nuestra aplicación principal tecleamos:

    (venv) $ flask db init
    creating directory /14/migrations ...  done
      Creating directory /14/migrations/versions ...  done
      Generating /14/migrations/alembic.ini ...  done
      Generating /14/migrations/README ...  done
      Generating /14/migrations/env.py ...  done
      Generating /14/migrations/script.py.mako ...  done
      Please edit configuration/connection/logging settings in '/14/migrations/alembic.ini' before proceeding.
    

    Después de haber ejecutado este comando te encontrarás un nuevo directorio en el directorio de trabajo llamado migrations con unos cuantos archivos y un subdirectorio llamado versions (que contendrá las modificaciones que hagamos a la estructura de la base de datos)

    Cuando hagamos una migración, Alembic, comparará la estructura almacenada en sus directorios con la actual que tengamos definida en nuestro programa y realizará los cambios. Como esta es la primera vez que ejecutaremos el comando y previamente no hay nada, lo que hará será crear tanto el archivo de la base de datos como su estructura. Y lo creará en el directorio y con el nombre que hayamos especificado dentro del archivo de configuración del proyecto, en nuestro caso en:

    SQLALCHEMY_DATABASE_URI = 'sqlite:///{}/database/dbase.db'.format(PWD)

    Vamos a ello:

    (venv) $ flask db migrate -m "primera migracion"
    /14
    INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
    INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
    INFO  [alembic.autogenerate.compare] Detected added table 'empleados'
      Generating /14/migrations/versions/0dd11e9faf8b_primera_migracion.py ...  done
    

    El programa acaba de generar un script para poder migrar la base de datos con un identificador único 0dd11e9faf8b (en tu caso será otro distinto). El comentario de la opción -m es opcional y añade una breve descripción de texto a la migración.

    Ahora podemos hacer dos cosas:

    1) Aplicar la función upgrade() que aplicará la migración, con todas sus modificaciones
    2) Si nos hemos confundido o no nos interesa hacerla podemos aplicar la función downgrade() y eliminaremos los cambios y volveremos al punto anterior.

    Como queremos consolidar los cambios utilizaremos upgrade de esta forma:

    (venv) $ flask db upgrade
    /14
    INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
    INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
    INFO  [alembic.runtime.migration] Running upgrade  -> 0dd11e9faf8b, primera migracion
    
    Y listo ya tenemos nuestra base de datos, dbasedb, con sus tabla y sus registros preparados y listos para trabajar. En resumen, para crear o actualizar la estructura de nuestra base de datos tenemos que primero generar el script de migración con flask db migrate y posteriormente consolidarla con flask db update.

    El utilizar flask-migrate, es muy útil porque si por ejemplo tienes el mismo proyecto en dos ordenadores, una vez generado el script de migración en el primero "flask migrate", lo puedes copiar y llevar al otro y automáticamente cuando apliques el upgrade en el segundo consultara sus versiones, verá si la que le has puesto es la más actual y aplicará los cambios.


    --------------------------------------------------------------------


    Bien, hecha la matización, volvemos a la rama principal de este capítulo. 

    Si ahora miras dentro del directorio database, SORPRESA, deberías ver como se ha creado el archivo dbase.db. Por si te sirve yo estoy usando para verlo un gestor de base de datos llamado "DB Browser for SQlite"

    Pagina Principal de la Aplicación.


    Lo primero que veremos nada mas entrar en la aplicación, va a ser nuestra pantalla principal. Tendrá un aspecto como este:


    La vista inicial estará formada por el siguiente código:


    Asignamos todos los registros que tenemos en la base de datos a la variable empleados con la instrucción Empleados.query.all() y se la pasamos a la plantilla main.html.

    Esta plantilla tendrá un botón en la parte superior derecha que nos permitirá añadir un nuevo empleado. Al pulsarlo nos redirigirá a la dirección o vista create.html


    Por otra parte, en la plantilla principal "main.html" y través de una tabla, vamos a usar un bucle {% for ...%}{% endfor %} para recorrer todos los registros de la base de datos que le hemos pasado en la variable empleados, con sus correspondientes campos (nombre, teléfono y correo electrónico)



    Además como última columna añadimos dos botones para poder "Editar" o "Borrar" cada registro. Tienes el código completo al final del capítulo.

    Añadir registros a la base de datos.


    Lo primero que he hecho es crear un formulario, con los archivos form.py y create.htlm, donde se recojerán los datos que necesitamos. Como ya hemos visto como se crean y usan los formularios, no voy a pararme a explicarlo. Puedes encontrar el código al final del capitulo y ver el post correspondiente a crear formularios.

    Quedará de la siguiente forma:



    Vamos a centrarnos en la vista. 

                                        @app.route('/create/', methods=["GET", "POST"])
                                            def create_item():
                                                form = Crear_Registro()
                                                if form.validate_on_submit():
                                                    # empleado es una instancia de la clase Empleado
                                                    empleado = Empleados(
                                                        nombre=form.nombre.data,
                                                        correo=form.correo.data,
                                                        telefono=form.telefono.data)
                                                    try:
                                                        db.session.add(empleado)
                                                        db.session.commit()
                                                        return redirect(url_for('home'))
                                                    except:
                                                        return 'ha habido un problema'
                                                else:
                                                    return render_template('create.html', form=form)


    Creamos la ruta "/create/" y definimos la función para guardar nuestros registros create_item 

    Al pulsar en el botón de la pagina principal "Añadir nuevo empleado" nos redirigirá a esta vista. Entraremos usando el método GET con lo que se renderizará la plantilla create.html en donde le hemos pasado el formulario.

    Cuando el formulario este relleno y validado se mandará a la ruta usando el método POST. De ahí, creamos la variable empleado, instancia de la clase Empleados, que recogerá los valores de los campos enviados en el formulario (nombre, correo y teléfono).  El id no lo pasamos porque automáticamente lo va a crear la base de datos cuando añadamos los registros.

    En flask-SQLAlchemy, para guardar un objeto en la base de datos, este debe estar previamente asociado al objeto sessión. Por tanto para añadir un nuevo empleado a la base de datos, lo haremos con db.session.add(). Simplemente le pasamos  los campos que queremos añadir que ya están metidos en la variable empleado, en singular, que ahora mismo es un objeto de la clase Empleados. 

    Bien, le hemos dicho a la base de datos que queremos añadir un registro pero como en toda base de datos tenemos que consolidarla, sino quedará sin hacer. Esto se consigue con la instrucción db.session.commit()

    Si todo ha ido bien volverás a la página de inicio "return redirect(url_for('home')" con el nuevo empleado ya guardado en la base de datos. Y como la página principal se encarga de listar todos nuestros empleados este registro se mostrará automáticamente.

    Borrar registros de la base de datos.


    Cada empleado en la página principal tiene un botón rojo a la derecha que nos permitirá borrarlo de la base de datos. 

    Al pulsarlo te saldrá un mensaje de advertencia para confirmar la acción. 


    Esto se consigue en main.html usando el atributo: onclick="return confirm('¿Estás seguro de querer borrar el registro?')" en la etiqueta <a> que usamos para crear el botón.

    Un punto importante es que al usar este botón, este nos redireccione a una vista que nos permita borrar ese empleado en concreto, para lo cual tendremos que indicar el id del mismo como parámetro. Por ejemplo si sitúas el ratón sobre el botón borrar del empleado cuyo Id es igual a 2, y te fijas en la parte inferior izquierda de la pantalla, veras que nos esta indicando la url a la que nos redirigirá cuando lo pulsemos.

    Pej. 127.0.0.1:5000/delete/2/

    Es decir enviamos 2 como parámetro a la URL /delete/. Para conseguir esto, cuando en main.html diseñamos el botón de cada registro, dentro del bucle for  tenemos que utilizar:

    <a href="{{url_for('delete_item', id=item.id)}}">
    o
    <a href="/delete/{{item.id}}/"

    Para que esto funcione tenemos que diseñar también la vista /delete/ para que acepte un parámetro, el id, que será un número entero.




    Una vez que tenemos la id del registro borrarlo es muy sencillo. Creamos primero la función delete_item, al que tenemos que pasarle el parámetro id y una variable empleado que recoja el resultado de buscar un registro dentro de Empleados filtrado por su campo id y lo ordenamos que lo borre:

    empleado = Empleados.query.filter_by(id=id).delete()

    y confirmamos el cambio:

    db.session.commit()

    Una vez hecho lo anterior volverá a la pagina principal (home) donde ser volverán a renderizar todos los registros actualizados de la base de datos.


    Actualización de un registro existente en la base de datos.


    La forma de hacerlo es parecida a la del punto anterior. Empezamos programando el botón amarillo para editar el registro en la pantalla principal. Al pulsarlo nos tiene que enviar a la URL /update/ enviando como parámetro el numero de id. Para editar por ejemplo el empleado con id=2 sería:

    127.0.0.1/update/2

    El código del botón quedaría en main.html como:

    <a href="{{url_for('update_item', id=item.id)}}">
    o
    <a href="/update/{{item.id}}>

    La vista en el inicio.py:



    La primera vez que entramos en la vista, lo hacemos usando el metodo GET y le pasamos el id del registro. Buscamos los datos del empleado asociado al id enviado con:

    empleado = Empleados.query.filter_by(id=id).first()

    Al usar filter_by() necesitamos decirle que registro queremos seleccionar. Aunque en este caso solo hay uno, ya que el id del empleado es único, tenemos que especificar que el primero .first()

    Luego renderizamos la plantilla 'update.html' enviando un formulario y el id. El formulario es el mismo que creamos para introducir un nuevo empleado con el matiz de que le pasamos, no un formulario vacío esta vez, sino que lo rellenamos con los datos que hemos obtenido con la consulta a la base de datos.



    Ahora modificamos lo que queramos en el formulario y le damos a enviar.  El formulario se enviará a la Url anterior volviendo a enviar de vuelta como parámetro el id y esta vez con el método POST.

    <form action="{{url_for('update_item', id=id)}}" method="post">

    Como el formulario ha sido validado y enviado, el condicional form.validate_on_submit() será verdadero y se ejecutará el código de su interior.

    Volvemos a buscar el empleado cuyo id se corresponde con el enviado por el formulario. Esta vez lo podemos hacer de otra forma con, por ejemplo:

    empleado = Empleados.query.get(id)

    e identificamos cada campo de la base de datos del empleado (nombre, correo y teléfono), con lo obtenido del formulario.

                                                empleado.nombre = form.nombre.data
                                                empleado.correo = form.correo.data
                                                empleado.telefono = form.telefono.data

    y ya lo tenemos, solo nos queda consolidarlo y listo.

    db.session.commit()

    Volveremos a la página inicial con el dato ya modificado.








    No hay comentarios:

    Publicar un comentario