M6 - P1 - App Web Con Flask y Base de Datos SQLite3 - v3
M6 - P1 - App Web Con Flask y Base de Datos SQLite3 - v3
PYTHON
APP WEB CON BBDD
PRÁCTICA 1-M6
• Crear tareas
• Marcar como completada una tarea
• Eliminar tareas
Para terminar con esta introducción, veamos un pantallazo del resultado de este proyecto:
5. Primera ejecución..................................................................................................................................................................19
En un mundo ideal, se trabajaría en todos los proyectos con la misma versión de Python y con los
mismos módulos o librerías. Pero la realidad es muy distinta, cada proyecto es totalmente diferente
y utiliza versiones de Python o versiones de módulos o librerías diferentes. Por lo tanto, si se
instalara en un sistema la versión de Python 3.6.2 por ejemplo y unos módulos o librerías
determinados, todos los proyectos tendrían que utilizar esa versión de Python como ese listado de
módulos y librerías instaladas. Esto, evidentemente, no es funcional ni práctico. Por eso, Python
dispone de los entornos virtuales, lo que proporciona crear un entorno totalmente nuevo y limpio
para cada proyecto. Pudiendo de esta forma, tener en un único sistema, en un único equipo, multitud
de entornos virtuales para multitud de proyectos, y donde cada entorno virtual estará configurado
de una manera. Ejemplo:
• Entorno virtual 1: Python 3.6.2 con el módulo SQLAlchemist (v2.5) y Pandas (v1.2)
• Entorno virtual 2: Python 3.1 con el módulo SQLAlchemist (v2.0) y Pandas (v1.2)
• Etc.
Esta es la forma en la que se trabaja profesionalmente, utilizando entornos virtuales para los
proyectos. Por lo que se va a crear este proyecto siguiendo esta metodología.
1. Abrir el IDE de Python con el que se programará. En este caso, será Pycharm
2. Crear un nuevo proyecto
• File > New Project… >
• Indicar ubicación y nombre del proyecto, en este caso, GestorTareas y en la ubicación
por defecto, en la carpeta de proyectos de PyCharm, dentro de una carpeta que he
creado previamente llamada Modulo_6
3. Seleccionar “New environment using > Virtualenv”
4. Marcamos la casilla de “Create a main.py welcome script”
Antes de comenzar a instalar los módulos hay que verificar que el terminal se encuentra en el
entorno virtual correcto (fijarse en el (venv) del inicio).
Es importante conocer el nombre de los módulos que se van a instalar. Se tienen varías formas para
conocer estos nombres, buscando en google, visitando las webs oficiales de los módulos, o visitando
www.pypi.org la cual se trata de un gran repositorio que almacena y documenta todos los módulos
open source de Python. En la bibliografía se encuentran los enlaces directos a cada uno de los
módulos que se utilizan en este proyecto.
2. Se podrá realizar una comprobación de que la instalación ha sido correcta y revisar que flask
se ha instalado yendo a File > Settings > Project: GestorTareas > Project Interpreter:
3. Instalación del módulo Flask SQL Alchemist, el cual permitirá poder manejar SQL desde el
servidor web Flask sin necesidad de profundizar en el lenguaje SQL. Para instalar este
módulo se volverá al terminal y ejecutaremos:
pip install Flask-SQLAlchemy
Si se cierra el IDE, en este caso Pycharm, también se cierra el entorno virtual en el que se está
trabajando. Cuando se vuelve a abrir Pycharm entrará automáticamente al entorno virtual para
seguir trabajando. Pero es conveniente comprobarlo. Esto se realiza siguiendo los siguientes pasos:
3. Si aparece un (venv) de virtual environment delante del Shell del proyecto, todo está correcto
Aunque no existe ninguna restricción técnica a la hora de escoger el nombre de nuestros ficheros
Python, hay ciertos estándares de la comunidad que nos indican los nombres estándar que se suelen
utilizar, por ejemplo, en páginas web HTML, se suele poner index.html como fichero principal, o en
hojas de estilo, se suele poner main.css. En Python también tenemos algunos nombres preferidos
por la comunidad, como son app o main. Para este proyecto usaremos el fichero main.py que se creó
automáticamente con la creación del proyecto. En caso de que no se hubiera creado al inicio, seguir
los siguientes pasos:
Ver las capturas que se muestran a continuación para seguir los pasos.
2. El primer objetivo, dado que esto es un proyecto web, es activar el servidor web, a
continuación, se muestra el código mínimo para implementar un servidor web con Flask:
if __name__ == '__main__':
app.run(debug=True) # El debug=True hace que cada vez que reiniciemos el
servidor o modifiquemos codigo, el servidor de Flask se reinicie solo
Esto se trata de una particularidad de Pycharm (que no ocurre en todas las versiones), con esto no
se ha instalado el módulo flask, ya que el módulo flask ya estaba instalado. De hecho, se podría omitir
este paso y se comprobaría con los pasos siguientes como flask si se encontraba instalado
correctamente. Lo que se ha instalado con este paso son unos complementos internos de Pycharm
para flask. Lo más relevante de este paso es que se elimina el mensaje de error tan molesto sobre
el from flask import Flask.
Nota: En ocasiones, las líneas rojas de error permanecen, aunque se encuentre correctamente
instalado.
Pero en este proyecto se va a intentar realizar todo de la forma más profesional posible, por lo que
se ejecutará el proyecto de la siguiente forma:
Se comprobará que no aparezcan errores y se puede ver en lo que muestra la ejecución por terminal
que el servidor web de Flask se encuentra activo (se dice comúnmente, que está corriendo) en la
siguiente dirección:
https://127.0.0.1:5000/
Esta dirección hace referencia al ordenador local en el que se encuentra ejecutándose este proyecto,
por lo que, lo que se debe hacer es:
El mensaje de Not Found no debe preocupar, simplemente es debido a que se ha creado un servidor
web en Flask pero no se ha asignado ningún tipo de contenido, por lo tanto, el servidor web de Flask
no encuentra nada que mostrar.
¡IMPORTANTE!
Para detener este servidor web que se acaba de ejecutar, no sirve cerrando la web del navegador.
Hay que volver al terminal de Pycharm y presionar Control + C para finalizar la ejecución del servidor
web.
En este punto se va a crear el contenido que mostrará automáticamente nada más iniciar el servidor
web de Flask. Es decir, la URL inicial, o lo que es lo mismo, en lenguaje de Flask, la ruta inicial.
Si ya se tenía activo el servidor web, al realizar este cambio, el servidor se habrá reiniciado
automáticamente. Si se tenía el servidor web apagado se deberá ir al terminal de Pycharm,
comprobar que se encuentra en el entorno virtual del proyecto y ejecutar python main.py
Y se volverá a:
Flask no viene con ninguna base de datos integrada. Y esto que en principio puede parecer un
inconveniente es, en realidad, una gran ventaja. En la actualidad existen, principalmente, dos grandes
familias de bases de datos: las relacionales (PostgreSQL, MySQL, Oracle, …) y las NoSQL (MongoDB,
CouchDB, Cassandra, …). Utilizar un tipo u otro en nuestra aplicación dependerá de múltiples factores,
como la escalabilidad o asegurar la fiabilidad de los datos. Gracias a que Flask no integra por defecto
ninguna base de datos, podemos usar en nuestra aplicación la que mejor satisfaga nuestras
necesidades en cada momento.
En este proyecto se utilizará SQLite, una base de datos relacional muy popular. Fundamentalmente,
las bases de datos relacionales se caracterizan por utilizar el lenguaje SQL como lenguaje para
realizar consultas y estar compuestas de varias tablas. A su vez, estas tablas están formadas por un
conjunto de campos (columnas) y registros (filas). Otra propiedad muy característica es que entre las
tablas se establecen relaciones.
Un ORM (Object-Relational Mapper) es una herramienta que nos ayuda a trabajar con las tablas de
la base de datos como si fueran objetos, de manera que cada tabla se mapea con una clase y cada
columna con un campo de dicha clase. Además, también nos permite mapear las relaciones entre
tablas como relaciones entre objetos.
No obstante, una de las características que más atractivos hacen a los ORMs es que puedes cambiar
tu base de datos sin apenas modificar código. De manera que puedes programar tu aplicación con
un ORM sin pensar que la base de datos es, por ejemplo, PostgreSQL, MySQL o SQLite.
Comencemos. Para organizar nuestro proyecto de la mejor forma posible, vamos a crear un fichero
Python que contenga toda la configuración inicial de la base de datos:
1. Creamos en nuestro proyecto el fichero db.py (en el mismo nivel que main.py)
engine = create_engine('sqlite:///database/tareas.db',
connect_args={'check_same_thread': False})
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base()
Lo primero se crea el engine, el motor que permite manejar la conexión con la base de datos y el
dialecto (el tipo de base de datos) que se utiliza, en este caso sqlite: "sqlite:///"
El argumento añadido al engine es para evitar posibles errores en caso de que la base de datos
ejecute varias acciones simultaneas y genere varios hilos de ejecución. Es imprescindible ponerlo
para evitar errores o warnings.
Vamos a crear una base de datos que se llame tareas.db que se encuentre en una carpeta que se
llame database. Estos nombres se podrían cambiar y la base de datos no es necesario que se
encuentre dentro de una carpeta, pero lo organizamos así pensando en proyectos de mayor tamaño.
Esta carpeta y el fichero de la base de datos lo crearemos más adelante.
3. Creación del fichero de la base de datos. Para ello se seguirán los siguientes pasos:
a. Clic derecho sobre el directorio principal del proyecto
b. Clic en New > Directory
No devuelve nada por lo que se confirma que la base de datos tareas.db está limpia.
5. Salir de SQLite
a. Finalmente, se sale de SQLite al terminal normal con el comando .exit (lleva un
punto delante)
En este punto se tiene la base de datos creada y vinculada al proyecto, pero aún no se puede probar
porque para ello se necesita poder añadir o eliminar datos de ella. Por lo que el siguiente paso es
crear una estructura y unas funcionalidades que sirvan para este objetivo.
Si no aparece HTML File, se creará como File y se le pondrá extensión .html al fichero.
c. Se indica el nombre index, se selecciona HTML 5 file y pulsa enter. El nombre index
no es obligatorio, pero es un estándar que el fichero raíz de una página web se llame
index.html
4. Probar lo implementado
5. Al igual que con el directorio templates, se deberá crear otro directorio llamado static que
incluirá aquellos componentes gráficos que son estáticos, como fuentes, imágenes, colores,
hojas de estilo CSS, etc.
6. Y al igual que con el fichero index.html, se creará dentro de static el fichero main.css. El cuál
será la hoja de estilo (stylesheet) principal del diseño de la web. Y de igual forma que
Para que se entienda, Los ficheros HTML son la estructura y contenido de una web. Las hojas de
estilo (ficheros CSS) son las que aportan el diseño a la web (colores, fuentes, márgenes, estilos de
texto, efectos, etc.)
Cada vez que se ejecute el fichero index.html se ejecutará esta línea, la cual realiza una llamada al
main.css para cargar los estilos allí contenidos.
9. Probar lo implementado
Borrar el contenido que se ha escrito en main.css, ya que únicamente se trataba de una prueba
Como se ha indicado anteriormente, este manual no está centrado en HTML y CSS. Se podría diseñar
manualmente todos los componentes gráficos que se quieren para la web, pero tomaría una gran
cantidad de tiempo y explicaciones.
Si careces de los conocimientos de HTML y CSS, se recomienda aprenderlos, pero para este
proyecto, se va a solucionar de otra manera. Se utilizará la popular librería Bootstrap, una gigantesca
librería de componentes gráficos ya creados que pueden ser utilizados en nuestras páginas web.
Esta librería dispone desde botones, hasta formularios, efectos de imágenes o videos, y un largo
etcétera.
Para utilizar Bootstrap únicamente se tendría que ir a Get started y copiar la etiqueta <link rel …>
que se encuentra al inicio de la página. Y se deberá pegar en el mismo lugar donde añadimos la
etiqueta <link rel> que vinculaba al fichero main.css.
Bootstrap es fantástico porque se pueden utilizar sus componentes gráficos de una manera muy
rápida, pero tiene un inconveniente, y es que si se quieren personalizar (cambiar de colores, fuentes,
bordes, etc.) involucra un tiempo mayor. Por eso, para este manual se va a utilizar un servicio que
nos va a proporcionar temas ya personalizados de Bootstrap. De esta forma se puede obtener algo
más de personalización en nuestros proyectos, sin mucho esfuerzo y sin que nuestro estilo sea el
estándar de Bootstrap.
Pasos a seguir:
Es recomendable añadir comentarios (sobre todo cuando se utilizan recursos externos). Para hacerlo
se deben utilizar los caracteres <!--COMENTARIO -->
En este punto, se va a comenzar a aplicar todos los recursos de los que se disponen para ir diseñando
la web.
<body>
<main class="container p-4">
<h1 class="display-4 text-center mt-4">Estoy en index.html</h1>
</main>
</body>
Estas clases que se añaden, son los estilos particulares que proporciona Bootstrap, para conocerlos
hay que investigar la documentación (enlace en la bibliografía). Por ejemplo, la clase display-4 hace
referencia a lo siguiente:
Ahora se va a hacer uso de la fuente de Google Fonts y para utilizarla, vamos a main.css
.titulo {
font-family: 'Permanent Marker', cursive;
}
Lo que se ha hecho en la instrucción anterior es crear una clase llamada titulo la cual integra la
utilización de la fuente Permanent Marker.
3. Se vuelve a modificar el fichero index.html del proyecto y se añade la clase título a la etiqueta
h1 del título que se desea insertar:
4. Probar lo implementado:
6. En este caso, se quiere que sea un fondo sutil, por lo que se seleccionará en la categoría de
clear y se selecciona el degradado Dull
8. Por último, se volverá al fichero main.css y se pegará el código del gradiente dentro de las
etiquetas body{}
.titulo {
font-family: 'Permanent Marker', cursive;
}
Fijarse que la ventaja de incluir este degradado vía CSS, es que es más compatible con más
navegadores y que es responsive, es decir, se ajusta y se adapta al tamaño de la ventana del
navegador. Probad a hacer más grande o más pequeña la ventana del navegador y observad como
el gradiente se va adaptando para que se sigan viendo sus colores de inicio y de fin y la transición
que los componen.
<div class="row">
<div class="col-md-4 offset-md-4 my-auto"> <!--Este div ocupará 4
columnas del espacio (centrado)-->
<div class="card"> <!--Creacion del objeto card-->
<div class="card-header">
</div>
<div class="card-body">
</div>
</div>
</div>
</div>
2. Probar lo implementado
3. Ahora, se implementará el formulario que habrá dentro del card header para que el usuario
pueda introducir texto. Además de un botón para confirmar esa tarea introducida por el
usuario en el campo del formulario. Para ello se realizará el siguiente código:
4. Probar lo implementado
En este punto, el usuario puede introducir texto en el campo del formulario y pulsar Enter o pulsar
en el botón Guardar y esto tendrá que almacenar dicha tarea. Como eso aún no se tiene programado,
si se escribe algo y se pulsa en Guardar, la página no hace nada y la URL cambia y se añade un
interrogante al final:
http://127.0.0.1:5000/?
Esto es porque no sabe qué hacer o a donde ir cuando se pulsa Guardar. A continuación, se va a
realizar esta implementación.
Vamos a crear un fichero llamado models.py de la misma forma que creamos db.py y en él
definiremos nuestro modelo de datos, es decir, las clases que vamos a utilizar en nuestro proyecto
y que a la vez actuarán como tablas de nuestra base de datos. En este caso, solo tendremos una
clase, la clase Tarea la cual tendrá un id, un contenido (el texto de la tarea) y una variable booleana
que indicará si la tarea está hecha o no.
Es muy importante que esta clase esté vinculada a la base de datos, para que, de esta manera,
cuando creemos un objeto de esta clase, ese objeto se almacene directamente en la base de datos.
Vamos a seguir los siguientes pasos:
2. Hacer que este fichero pueda acceder a la inicialización, configuración y variables de la base
de datos (los cuales se encuentran en el fichero db.py). Añadiremos al inicio del fichero
models.py lo siguiente:
import db
from sqlalchemy import Column, Integer, String, Boolean
Al hacer este import db estamos importando el contenido del fichero db.py a este fichero,
por lo que, se tendrá acceso a sus variables. Por otro lado, también añadiremos unos imports
de sqlalchemy que serán necesarios para definir los atributos de mi clase / tabla
'''
Creamos una clase llamada Tarea
Esta clase va a ser nuestro modelo de datos de la tarea (el cual nos servirá
luego para la base de datos)
Esta clase va a almacenar toda la información referente a una tarea
'''
class Tarea(db.Base):
__tablename__ = "tarea"
id = Column(Integer, primary_key=True) # Identificador único de cada tarea
(no puede haber dos tareas con el mismo id, por eso es primary key)
contenido = Column(String(200), nullable=False) # Contenido de la tarea, un
texto de máximo 200 caracteres
hecha = Column(Boolean) # Booleano que indica si una tarea ha sido hecha o no
def __repr__(self):
return "Tarea {}: {} ({})".format(self.id, self.contenido, self.hecha)
def __str__(self):
return "Tarea {}: {} ({})".format(self.id, self.contenido, self.hecha)
Se han llamado a las variables del modelo id, contenido y hecha. Estos nombres, son nombres
de variables, se pueden poner los que se quiera (teniéndolos en cuenta posteriormente claro).
El objetivo es que la etiqueta input del formulario envíe su contenido a otro sitio. Se ha visto
anteriormente que Flask trabaja con rutas, por lo que se tendrá que crear una ruta para recibir
esta información. El proceso completo de guardar una tarea sería:
1. En index.html, añadir el atributo name al input del formulario. Es muy importante ya que a
través de este atributo es por donde se accede al contenido del input. Tras añadir el atributo
name deberá quedar así (se le ha asignado el nombre contenido_tarea pero este es un
nombre de variable, podría ser cualquier nombre):
2. Tener el fichero models.py listo y con el modelo de datos creado, en este caso, la clase Tarea
3. Añadir un import al fichero main.py que permitirá acceder a las variables y parámetros del
fichero db.py:
5. Con lo anterior, se tiene importado el modelo y se tiene acceso a él, pero no se tiene creado,
por eso, como último paso, se creará el modelo, es decir, se crearán las tablas. Esto se va a
hacer con una instrucción que se tiene que añadir en el main:
if __name__ == '__main__':
db.Base.metadata.create_all(db.engine) # Creamos el modelo de datos
app.run(debug=True) # El debug=True hace que cada vez que reiniciemos el
servidor o modifiquemos código, el servidor de Flask se reinicie solo
Es importante respetar el orden, primero se crea el modelo y luego se arranca el servidor web.
6. Probemos lo implementado hasta este punto para verificar que se crea la tabla tarea. Se
ejecuta la app de nuevo y se abre un nuevo terminal y se comprueba la base de datos (se
pueden abrir tantos terminales como se desee, ya que tenemos el símbolo de + en la parte
superior, que abre nuevos terminales):
Como se observa, ahora la base de datos tareas.db contiene una tabla llamada tarea.
Si se desea ver los nombres de las columnas se pueden obtener con la siguiente consulta:
@app.route('/crear-tarea', methods=['POST'])
def crear():
# tarea es un objeto de la clase Tarea (una instancia de la clase)
tarea = Tarea(contenido=request.form['contenido_tarea'], hecha=False) # id no
es necesario asignarlo manualmente, porque la primary key se genera
automáticamente
8. Siguiendo en main.py seguimos ampliando el método crear() para que el contenido recibido
a través del input del formulario se almacene en la tabla tarea de la base de datos:
@app.route('/crear-tarea', methods=['POST'])
def crear():
# tarea es un objeto de la clase Tarea (una instancia de la clase)
tarea = Tarea(contenido=request.form['contenido_tarea'], hecha=False) # id no
es necesario asignarlo manualmente, porque la primary key se genera
automaticamente
db.session.add(tarea) # Añadir el objeto de Tarea a la base de datos
db.session.commit() # Ejecutar la operación pendiente de la base de datos
return "Tarea guardada" # Mensaje de log para ver a través del navegador
9. Por último, hay que indicarle al formulario de index.html que tiene que enviar los datos a la
ruta que se acaba de crear (/crear-tarea). Para ello se rellenará el <form action=””> que
anteriormente se había dejado vacío.
Se puede ver que de forma correcta nos muestra por pantalla el texto que se había incluido en el
return y se ve que, efectivamente, nos ha llevado a la ruta /crear-tarea.
11. Comprobar que se haya almacenado la tarea en la base de datos. Para ello se abre de nuevo
un terminal y se comprueba la base de datos con sqlite3:
Como se puede observar, si se realiza una consulta donde se piden todos los datos de la tabla
tarea, nos aparece la tarea que se acaba de registrar desde la web.
Ahora la aplicación ya es capaz de guardar información desde el formulario web a la base de datos.
Este sería uno de los requisitos principales en el proyecto. Pero otro sería el poder visualizar desde
la web las tareas almacenadas en la base de datos. Es decir, que, desde la web, se tengan las dos
funcionalidades principales:
1. Se vuelve a main.py, concretamente a la función home() la cual se ejecuta cada vez que se
carga la web. Y es en este punto donde se debe realizar una consulta a la base de datos y
guardar en una variable todas las tareas de la base de datos.
def home():
todas_las_tareas = db.session.query(Tarea).all() # Consultamos y almacenamos
todas las tareas de la base de datos
# Ahora en la variable todas_las_tareas se tienen almacenadas todas las
tareas. Vamos a entregar esta variable al template index.html
return render_template("index.html", lista_de_tareas=todas_las_tareas) # Se
carga el template index.html
2. Ahora, en index.html, se recibe el listado de todas las tareas que dispone la base de datos,
lo que se realizará será mostrarlas por pantalla para que el usuario pueda verlas. El lugar
donde se mostrará esta información será en el <div class=”card-body”> que aún permanece
vacío.
Lo ideal sería modificar esta segunda funcionalidad para que cuando se guarde una nueva tarea:
Así que se va a proceder a realizar esta modificación, para ello habrá que dirigirse a main.py
En este punto ya se dispone de una aplicación bastante funcional, pero se desea poder interactuar
con las tareas de la lista, para poder indicar si están hechas o no. Para ello, recordad que cuando se
creó el modelo de datos de la clase Tarea, le añadimos un atributo booleano que se llamaba hecha.
Con esta variable se podrá confirmar si una tarea se encuentra hecha o no. Pero lo primero, se va a
mejorar el estilo de la lista.
<div class="card-body">
<!-- Las etiquetas <ul> y <li> sirven para crear listas en HTML -->
<ul class="list-group">
<!-- Gracias a Jinja se puede introducir código Python en nuestro HTML y
Python se encarga de ejecutarlo e interpretarlo -->
{% for tarea in lista_de_tareas %}
<li class="list-group-item">
{{tarea.contenido}} <!-- contenido es la variable de la clase Tarea
que almacena el texto de la tarea -->
<a href="" style="text-decoration:none" class="btn btn-sucess btn-
sm"> Hecho </a>
<a href="" style="text-decoration:none" class="btn btn-danger btn-
sm"> Eliminar </a>
</li>
{% endfor %}
</ul>
</div>
Esto generará dos botones, uno con la palabra Hecho y otro con la palabra Eliminar.
Pero se va a mejorar el diseño, haciendo que estos botones sean iconos. Para ello se usarán los
iconos de Font Awesome, una web enorme de recursos gráficos para el desarrollo web. Pero no
será necesario salir de bootstrap ya que bootstrap da acceso a estos recursos.
Esta instrucción ha sido obtenida de la misma web donde obtuvimos el tema para bootstrap:
https://www.bootstrapcdn.com/fontawesome/
Hay una etiqueta HTML que ya proporciona la instrucción <link> completa como se puede
observar en la siguiente captura:
<div class="card-body">
<!-- Las etiquetas <ul> y <li> sirven para crear listas en HTML -->
<ul class="list-group">
<!-- Gracias a Jinja se puede introducir codigo Python en nuestro HTML y
Python se encarga de ejecutarlo e interpretarlo -->
{% for tarea in lista_de_tareas %}
<li class="list-group-item">
{{tarea.contenido}} <!-- contenido es la variable de la clase Tarea
que almacena el texto de la tarea -->
<a href="" style="text-decoration:none">
<svg class="bi bi-check-box" width="2em" height="2em" viewBox="0
0 16 16" fill="green" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M15.354 2.646a.5.5 0 010 .708l-7
7a.5.5 0 01-.708 0l-3-3a.5.5 0 11.708-.708L8 9.293l6.646-6.647a.5.5 0 01.708 0z"
clip-rule="evenodd"/>
<path fill-rule="evenodd" d="M1.5 13A1.5 1.5 0 003 14.5h10a1.5
1.5 0 001.5-1.5V8a.5.5 0 00-1 0v5a.5.5 0 01-.5.5H3a.5.5 0 01-.5-.5V3a.5.5 0 01.5-
.5h8a.5.5 0 000-1H3A1.5 1.5 0 001.5 3v10z" clip-rule="evenodd"/>
</svg>
</a>
<a href="" style="text-decoration:none">
<svg class="bi bi-trash" width="2em" height="2em" viewBox="0 0 16
16" fill="red" xmlns="http://www.w3.org/2000/svg">
<path d="M5.5 5.5A.5.5 0 016 6v6a.5.5 0 01-1 0V6a.5.5 0 01.5-
.5zm2.5 0a.5.5 0 01.5.5v6a.5.5 0 01-1 0V6a.5.5 0 01.5-.5zm3 .5a.5.5 0 00-1
0v6a.5.5 0 001 0V6z"/>
<path fill-rule="evenodd" d="M14.5 3a1 1 0 01-1 1H13v9a2 2 0
01-2 2H5a2 2 0 01-2-2V4h-.5a1 1 0 01-1-1V2a1 1 0 011-1H6a1 1 0 011-1h2a1 1 0 011
1h3.5a1 1 0 011 1v1zM4.118 4L4 4.059V13a1 1 0 001 1h6a1 1 0 001-1V4.059L11.882
4H4.118zM2.5 3V2h11v1h-11z" clip-rule="evenodd"/>
</svg>
</a>
</li>
{% endfor %}
</ul>
</div>
Para entender rápidamente este código, el resumen sería que hay una etiqueta <a href=””>
la cual genera un hipervínculo a otra página (de momento no se le ha indicado a donde). Y
dentro de la etiqueta <a href> se encuentra la etiqueta <svg> que construye por partes el
icono deseado. Tras la construcción del icono, se cierra la etiqueta svg con </svg> y se cierra
la etiqueta del enlace con </a>
Se han seleccionado dos, acorde con las funcionalidades de hecho y eliminar (enlaces en la
bibliografía):
• Antes
o width="16" height="16"
o fill="currentColor"
• Después
o width="2em" height="2em"
o fill="green" o fill="red" (en función del icono que sea)
4. Comprobar el resultado:
Ahora, únicamente falta que cuando se pulse en el icono del check, marque de alguna manera que
esa tarea esta completada. Y que cuando se pulse en el icono de eliminar, la tarea correspondiente
se elimine.
@app.route('/eliminar-tarea/<id>')
def eliminar(id):
tarea = db.session.query(Tarea).filter_by(id=int(id)).delete() # Se busca
dentro de la base de datos, aquel registro cuyo id coincida con el aportado por
el parametro de la ruta. Cuando se encuentra se elimina
db.session.commit() # Ejecutar la operación pendiente de la base de datos
return redirect(url_for('home')) # Esto nos redirecciona a la función home()
y si todo ha ido bien, al refrescar, la tarea eliminada ya no aparecera en el
listado
2. Modificar en el icono, a que dirección ir en caso de ser pulsado. Para ello se volverá a
index.html y se realizará la siguiente modificación en la parte del icono de eliminar:
Hay que fijarse que al poner el ratón encima de los iconos de eliminar, en la esquina inferior izquierda
aparece la ruta a la cual apuntan dichos iconos. Y se puede ver que las direcciones son:
• 127.0.0.1:5000/eliminar-tarea/1
• 127.0.0.1:5000/eliminar-tarea/2
5. Implementación de la acción a realizar cuando se pulse el icono de check (el del check en la
casilla). Lo que se hará es que cuando se pulse este icono, el texto de la tarea se tachará, y
si se vuelve a pulsar en el icono se eliminará ese tachado. Para esto, se va a crear una nueva
clase en main.css
.tarea_hecha {
text-decoration: line-through;
color: #cfcfcf;
}
6. A continuación, se creará una nueva ruta en main.py con el formato /tarea-hecha/<id>. Esto
significa que cuando se realice una llamada a http://127.0.0.1:5000/tarea-hecha/1 el
estado de la variable booleana que indica si la tarea está hecha o no cambiará. Si estaba a
true pasará a false, y si estaba a false pasará a true.
@app.route('/tarea-hecha/<id>')
def hecha(id):
tarea = db.session.query(Tarea).filter_by(id=int(id)).first() # Se obtiene la
tarea que se busca
tarea.hecha = not(tarea.hecha) # Guardamos en la variable booleana de la
tarea, su contrario
db.session.commit() # Ejecutar la operación pendiente de la base de datos
return redirect(url_for('home')) # Esto nos redirecciona a la función home()
<div class="card-body">
<!-- Las etiquetas <ul> y <li> sirven para crear listas en HTML -->
<ul class="list-group">
<!-- Gracias a Jinja se puede introducir codigo Python en nuestro HTML y
Python se encarga de ejecutarlo e interpretarlo -->
{% for tarea in lista_de_tareas %}
<li class="list-group-item">
<span class="{% if tarea.hecha==true %} tarea_hecha {% endif %}">
{{tarea.contenido}} </span>
<a href="/tarea-hecha/{{tarea.id}}" style="text-decoration:none">
<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em"
fill="green" class="bi bi-check2-square" viewBox="0 0 16 16">
<path d="M3 14.5A1.5 1.5 0 0 1 1.5 13V3A1.5 1.5 0 0 1 3
1.5h8a.5.5 0 0 1 0 1H3a.5.5 0 0 0-.5.5v10a.5.5 0 0 0 .5.5h10a.5.5 0 0 0 .5-
.5V8a.5.5 0 0 1 1 0v5a1.5 1.5 0 0 1-1.5 1.5H3z"/>
<path d="M8.354 10.354l7-7a.5.5 0 0 0-.708-.708L8 9.293 5.354
6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0z"/>
</svg>
</a>
<a href="/eliminar-tarea/{{tarea.id}}" style="text-decoration:none">
<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em"
fill="red" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1
.5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0
0-1 0v6a.5.5 0 0 0 1 0V6z"/>
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0
1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0
0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-
1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
</svg>
</a>
</li>
{% endfor %}
</ul>
</div>
9. Y se comprobará:
Crear varias tareas, algunas marcadas como finalizadas y otras como no:
O se puede utilizar un gestor visual para acceder a esta base de datos, como por ejemplo DB Browser
for SQLite
Hemos acabado este proyecto y tenemos dos dependencias, flask y flask-sqlalchemy. Pero en un
proyecto más grande es habitual encontrarse con varias decenas de dependencias.
Se podrá realizar una comprobación de las dependencias instaladas yendo a File > Settings > Project:
GestorTareas > Project Interpreter:
Esta forma funciona perfectamente, pero cuando se requiere instalar varias decenas de
dependencias es una tarea muy costosa y pesada. Por lo que una de las formas más comunes de
gestionar las dependencias es con el fichero requirements.txt. PyCharm nos ofrece además un
plugin para sincronizar este fichero cuando hacemos cambios en las librerías que vamos instalando
en nuestro proyecto. Veamos cómo crear este fichero y como utilizarlo.
3. Vamos a utilizar el plugin Requirements. Sino lo tenemos instalado, vamos a File -> Settings
-> Plugins y lo instalamos. Ahora vamos a Tools -> Sync Python Requirements y nos
aparecerá una ventana con unas opciones:
• Don’t specify version: hace que se instale siempre la última versión de cada librería
• Strong equality (==): fija una versión
• Greater or equal (>=): establece una versión mínima
• Compatible version (~=): Instala la última versión compatible. Lo normal es que una “major
versión” pueda no ser compatible, pero una “minor versión” o “patch versión” tienen que
serlo. Esta opción solo instala estas últimas versiones. Esta es la mejor opción para la
mayoría de casos.
• Remove unused requirements: por si sincronizamos el fichero más adelante y hemos dejado
de usar una dependencia, se borrar del fichero requirements.txt
• Modify base files: para que añada las librerías que tenemos instaladas en el fichero al
generarlo
• Keep existing version: mantiene la versión especificada si coincide con la actual
Con esto ya tendríamos nuestro fichero requirements.txt listo. Se pulsa en OK para crearlo.
¡ADVERTENCIA! En algunas versiones de Pycharm hay que realizar esta operación dos veces, la
primera crea el fichero y la segunda añade las dependencias.
4. Tras generar el fichero se comprueba que aparezca en la raíz del proyecto y que en su interior
se encuentran las dependencias:
1. Eliminar el entorno virtual del proyecto (con esto eliminamos todas las dependencias)
Python 3
https://www.python.org/
https://www.jetbrains.com/es-es/pycharm/download/#section=windows
https://flask.palletsprojects.com/en/1.1.x/
https://flask-sqlalchemy.palletsprojects.com/en/2.x/
https://pypi.org/project/Flask/
https://pypi.org/project/Flask-SQLAlchemy/
https://www.sqlite.org/index.html
Bootstrap
https://getbootstrap.com/
Bootstrap (documentación)
https://getbootstrap.com/docs/4.0/getting-started/introduction/
https://getbootstrap.com/docs/4.0/components/card/
Bootstrap CDN
https://www.bootstrapcdn.com/bootswatch/
https://fonts.google.com/
uiGradients
https://uigradients.com/
Jinja
https://jinja.palletsprojects.com/en/2.11.x/
https://www.bootstrapcdn.com/fontawesome/
Bootstrap. Iconos
https://icons.getbootstrap.com/
https://icons.getbootstrap.com/icons/check2-square/
https://icons.getbootstrap.com/icons/trash/