Cómo utilizar relaciones de base de datos muchos a muchos con Flask-SQLAlchemy

El autor seleccionó el Fondo de Software Libre y de Código Abierto para recibir una donación como parte del programa Escribir para Donaciones.

Introducción

Flask es un marco de trabajo web ligero de Python que proporciona herramientas y características útiles para crear aplicaciones web en el lenguaje Python. SQLAlchemy es un kit de herramientas SQL que proporciona acceso eficiente y de alto rendimiento a bases de datos relacionales. Proporciona formas de interactuar con varios motores de base de datos como SQLite, MySQL y PostgreSQL. Te da acceso a las funcionalidades SQL de la base de datos. También te proporciona un Mapeador Relacional de Objetos (ORM), que te permite hacer consultas y manejar datos usando objetos y métodos de Python. Flask-SQLAlchemy es una extensión de Flask que facilita el uso de SQLAlchemy con Flask, proporcionándote herramientas y métodos para interactuar con tu base de datos en tus aplicaciones de Flask a través de SQLAlchemy.

A many-to-many database relationship is a relationship between two database tables where a record in each table can reference several records in the other table. For example, in a blog, a table for posts can have a many-to-many relationship with a table for storing authors. Each post can have many authors, and each author can write many posts. Therefore, there is a many-to-many relationship between posts and authors. For another example, in a social media application, each post may have many hashtags, and each hashtag may have many posts.

En este tutorial, modificarás una aplicación construida usando Flask y Flask-SQLAlchemy agregando una relación de muchos a muchos. Tendrás una relación entre publicaciones y etiquetas, donde cada entrada de blog puede tener varias etiquetas y cada etiqueta puede tener múltiples entradas etiquetadas con ella.

Aunque puedes seguir este tutorial de forma independiente, también es una continuación del tutorial Cómo Usar Relaciones de Base de Datos Uno a Muchos con Flask-SQLAlchemy, en el cual construyes una base de datos de varias tablas con una relación uno a muchos entre publicaciones y comentarios en una aplicación de blogs.

Al final del tutorial, tu aplicación tendrá una nueva característica para añadir etiquetas a las publicaciones. Las publicaciones pueden ser etiquetadas con múltiples etiquetas, y cada página de etiquetas mostrará todas las publicaciones etiquetadas con ella.

Prerrequisitos

Paso 1 — Configuración de la Aplicación Web

En este paso, configurarás la aplicación de blogs para que esté lista para la modificación. También revisarás los modelos de base de datos de Flask-SQLAlchemy y las rutas de Flask para comprender la estructura de la aplicación. Si seguiste el tutorial en la sección de requisitos previos y aún tienes el código y el entorno virtual en tu máquina local, puedes omitir este paso.

Para demostrar cómo agregar una relación de muchos a muchos a una aplicación web Flask con Flask-SQLAlchemy, utilizarás el código de la aplicación del tutorial anterior, que es un sistema de blogs con la capacidad de agregar y mostrar publicaciones, comentar en publicaciones y leer y eliminar comentarios existentes.

Clona el repositorio y cámbiale el nombre de flask-slqa-bloggy a flask_app con el siguiente comando:

  1. git clone https://github.com/do-community/flask-slqa-bloggy flask_app

Navega a flask_app:

  1. cd flask_app

Luego crea un nuevo entorno virtual:

  1. python -m venv env

Activa el entorno:

  1. source env/bin/activate

Instala Flask y Flask-SQLAlchemy:

  1. pip install Flask Flask-SQLAlchemy

A continuación, establece las siguientes variables de entorno:

  1. export FLASK_APP=app
  2. export FLASK_ENV=development

FLASK_APP indica la aplicación que estás desarrollando actualmente, que es app.py en este caso. FLASK_ENV especifica el modo. Lo configurarás en development para el modo de desarrollo; esto te permitirá depurar la aplicación. Recuerda no utilizar este modo en un entorno de producción.

Luego, abre la consola de Flask para crear las tablas de la base de datos:

  1. flask shell

A continuación, importa el objeto de base de datos db de Flask-SQLAlchemy, el modelo Post y el modelo Comment, y crea las tablas de la base de datos utilizando la función db.create_all():

  1. from app import db, Post, Comment
  2. db.create_all()
  3. exit()

Luego, popula la base de datos usando el programa init_db.py:

  1. python init_db.py

Esto agrega tres publicaciones y cuatro comentarios a la base de datos.

Ejecuta el servidor de desarrollo:

  1. flask run

Si vas a tu navegador, tendrás la aplicación ejecutándose en la siguiente URL:

http://127.0.0.1:5000/

Verás una página similar a la siguiente:

Si recibes un error, asegúrate de haber seguido correctamente los pasos anteriores.

Para detener el servidor de desarrollo, usa CTRL + C.

A continuación, pasarás por los modelos de base de datos Flask-SQLAlchemy para entender las relaciones actuales entre tablas. Si estás familiarizado con el contenido del archivo app.py, puedes pasar al siguiente paso.

Abre el archivo app.py:

  1. nano app.py

El contenido del archivo es el siguiente:

flask_app/app.py
import os
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy

basedir = os.path.abspath(os.path.dirname(__file__))

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] =\
           'sqlite:///' + os.path.join(basedir, 'database.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False


db = SQLAlchemy(app)


class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100))
    content = db.Column(db.Text)
    comments = db.relationship('Comment', backref='post')

    def __repr__(self):
        return f'<Post "{self.title}">'


class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'))

    def __repr__(self):
        return f'<Comment "{self.content[:20]}...">'


@app.route('/')
def index():
    posts = Post.query.all()
    return render_template('index.html', posts=posts)


@app.route('/<int:post_id>/', methods=('GET', 'POST'))
def post(post_id):
    post = Post.query.get_or_404(post_id)
    if request.method == 'POST':
        comment = Comment(content=request.form['content'], post=post)
        db.session.add(comment)
        db.session.commit()
        return redirect(url_for('post', post_id=post.id))

    return render_template('post.html', post=post)


@app.route('/comments/')
def comments():
    comments = Comment.query.order_by(Comment.id.desc()).all()
    return render_template('comments.html', comments=comments)


@app.post('/comments/<int:comment_id>/delete')
def delete_comment(comment_id):
    comment = Comment.query.get_or_404(comment_id)
    post_id = comment.post.id
    db.session.delete(comment)
    db.session.commit()
    return redirect(url_for('post', post_id=post_id))

Aquí, tienes dos modelos de base de datos que representan dos tablas:

  • Post: que tiene una columna ID, un título, contenido y una relación de uno a muchos con la tabla de comentarios.

  • Comment: que tiene una columna ID, una columna para contenido y una columna post_id para hacer referencia al post al que pertenece el comentario.

Debajo de los modelos tienes las siguientes rutas:

  • /: La página de índice, que muestra todos los posts en la base de datos.
  • /<int:post_id>/: La página individual del post. Por ejemplo, el enlace http://127.0.0.1:5000/2/ muestra los detalles del segundo post en la base de datos y sus comentarios.
  • /comentarios/: Una página que muestra todos los comentarios en la base de datos y enlaces a la publicación en la que se publicó cada comentario.
  • /comentarios/<int:id_comentario>/eliminar: Una ruta que elimina un comentario a través de un botón Eliminar Comentario.

Cierre el archivo app.py.

En el próximo paso, utilizará una relación de muchos a muchos para crear un enlace entre dos tablas.

Paso 2 — Configuración de Modelos de Base de Datos para una Relación de Muchos a Muchos

En este paso, agregará un modelo de base de datos que representará la tabla de etiquetas. Lo vinculará con la tabla de publicaciones existente utilizando una tabla de asociación, que es una tabla que conecta sus dos tablas en una relación de muchos a muchos. Una relación de muchos a muchos vincula dos tablas donde cada elemento en una tabla tiene muchos elementos relacionados en la otra tabla. En la tabla de asociación, cada publicación hará referencia a sus etiquetas y cada etiqueta hará referencia a las publicaciones etiquetadas con ella. También insertará algunas publicaciones y etiquetas en su base de datos, imprimirá publicaciones con sus etiquetas e imprimirá etiquetas y sus publicaciones relacionadas.

Supongamos que tiene una tabla simple para publicaciones de blog de la siguiente manera:

Posts
+----+-----------------------------------+
| id | content                           |
+----+-----------------------------------+
| 1  | A post on life and death          |
| 2  | A post on joy                     |
+----+-----------------------------------+

Y una tabla para etiquetas así:

Tags
+----+-------+
| id | name  |
+----+-------+
| 1  | life  |
| 2  | death |
| 3  | joy   |
+----+-------+

Digamos que deseas etiquetar Una publicación sobre la vida y la muerte con las etiquetas vida y muerte. Puedes hacerlo agregando una nueva fila en la tabla de publicaciones de la siguiente manera:

Posts
+----+-----------------------------------+------+
| id | content                           | tags |
+----+-----------------------------------+------+
| 1  | A post on life and death          | 1, 2 |
| 2  | A post on joy                     |      |
+----+------------------------------------------+

Este enfoque no funciona, porque cada columna debería tener solo un valor. Si tienes múltiples valores, las operaciones básicas como agregar y actualizar datos se vuelven engorrosas y lentas. En cambio, debería haber una tercera tabla que haga referencia a las claves principales de las tablas relacionadas; esta tabla se llama a menudo una tabla de asociación o una tabla de unión, y almacena IDs de cada elemento de cada tabla.

Aquí tienes un ejemplo de una tabla de asociación que vincula publicaciones y etiquetas:

post_tag
+----+---------+-------------+
| id | post_id | tag_id      |
+----+---------+-------------+
| 1  | 1       | 1           |
| 2  | 1       | 2           |
+----+---------+-------------+

En la primera fila, la publicación con el ID 1 (es decir, Una publicación sobre la vida y la muerte) se relaciona con la etiqueta con el ID 1 (vida). En la segunda fila, la misma publicación también se relaciona con la etiqueta con el ID 2 (muerte). Esto significa que la publicación está etiquetada tanto con las etiquetas vida como muerte. Del mismo modo, puedes etiquetar cada publicación con múltiples etiquetas.

Ahora, modificarás el archivo app.py para agregar un nuevo modelo de base de datos que represente la tabla que usarás para almacenar etiquetas. También agregarás una tabla de asociación llamada post_tag que vincula publicaciones con etiquetas.

Primero, abre app.py para establecer una relación entre publicaciones y etiquetas:

  1. nano app.py

Agrega una tabla post_tag y un modelo Tag debajo del objeto db y arriba del modelo Post, luego agrega una pseudo-columna de relación tags al modelo Post para que puedas acceder a las etiquetas de una publicación mediante post.tags y acceder a las publicaciones de una etiqueta mediante tag.posts:

flask_app/app.py

# ...

db = SQLAlchemy(app)


post_tag = db.Table('post_tag',
                    db.Column('post_id', db.Integer, db.ForeignKey('post.id')),
                    db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
                    )


class Tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))

    def __repr__(self):
        return f'<Tag "{self.name}">' 



class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100))
    content = db.Column(db.Text)
    comments = db.relationship('Comment', backref='post')
    tags = db.relationship('Tag', secondary=post_tag, backref='posts')

    def __repr__(self):
        return f'<Post "{self.title}">'

Guarda y cierra el archivo.

Aquí utilizas la función db.Table() para crear una tabla con dos columnas. Para las tablas de asociación, la mejor práctica es usar una tabla en lugar de un modelo de base de datos.

La tabla post_tag tiene dos columnas que representan dos claves externas, que son claves que se utilizan para hacer referencia a columnas de clave primaria en otra tabla:

  • post_id: Una clave externa entera que representa el ID de la publicación y hace referencia a la columna ID en la tabla post.
  • tag_id: Una clave externa entera que representa el ID de la etiqueta y hace referencia a la columna ID en la tabla tag.

Estas claves establecen las relaciones entre tablas.

Debajo de la tabla post_tag, creas un modelo Tag, que representa la tabla en la que almacenarás tus etiquetas. Esta tabla de etiquetas tiene dos columnas:

  • id: El ID de la etiqueta.
  • name: El nombre de la etiqueta.

Utilizas el nombre de la etiqueta en el método especial __repr__() para dar a cada objeto de etiqueta una representación de cadena clara con fines de depuración.

Agrega una variable de clase tags al modelo Post. Utiliza el método db.relationship(), pasándole el nombre del modelo de etiquetas (Tag en este caso).

Pasa la tabla de asociación post_tag al parámetro secondary para establecer una relación de muchos a muchos entre publicaciones y etiquetas.

Utiliza el parámetro backref para agregar una referencia inversa que se comporta como una columna al modelo Tag. De esta manera, puedes acceder a las publicaciones de la etiqueta a través de tag.posts y a las etiquetas de una publicación a través de post.tags. Verás un ejemplo que demuestra esto más adelante.

A continuación, edita el programa Python init_db.py para modificar la base de datos agregando la tabla de asociación post_tag y la tabla de etiquetas que se basará en el modelo Tag:

  1. nano init_db.py

Edita el archivo para que se vea de la siguiente manera:

flask_app/init_db.py
from app import db, Post, Comment, Tag

db.drop_all()
db.create_all()

post1 = Post(title='Post The First', content='Content for the first post')
post2 = Post(title='Post The Second', content='Content for the Second post')
post3 = Post(title='Post The Third', content='Content for the third post')

comment1 = Comment(content='Comment for the first post', post=post1)
comment2 = Comment(content='Comment for the second post', post=post2)
comment3 = Comment(content='Another comment for the second post', post_id=2)
comment4 = Comment(content='Another comment for the first post', post_id=1)

tag1 = Tag(name='animals')
tag2 = Tag(name='tech')
tag3 = Tag(name='cooking')
tag4 = Tag(name='writing')

post1.tags.append(tag1)  # Etiqueta la primera publicación con 'animales'
post1.tags.append(tag4)  # Etiqueta la primera publicación con 'escritura'
post3.tags.append(tag3)  # Etiqueta la tercera publicación con 'cocina'
post3.tags.append(tag2)  # Etiqueta la tercera publicación con 'tecnología'
post3.tags.append(tag4)  # Etiqueta la tercera publicación con 'escritura'


db.session.add_all([post1, post2, post3])
db.session.add_all([comment1, comment2, comment3, comment4])
db.session.add_all([tag1, tag2, tag3, tag4])

db.session.commit()

Guarda y cierra el archivo.

Aquí importas el modelo Tag. Eliminas todo en la base de datos usando la función db.drop_all() para agregar las tablas de etiquetas y post_tag de manera segura y evitar cualquier problema común relacionado con la adición de nuevas tablas a una base de datos. Luego creas todas las tablas de nuevo usando la función db.create_all().

Después del código del tutorial anterior que declara las publicaciones y los comentarios, utilizas el modelo Tag para crear cuatro etiquetas.

Luego agregas etiquetas a las publicaciones usando el atributo tags que se agregó a través de la línea tags = db.relationship('Tag', secondary=post_tag, backref='posts') en el archivo app.py. Asignas etiquetas a las publicaciones usando un método append() similar a las listas de Python.

A continuación, agregas las etiquetas que creaste a la sesión de la base de datos usando la función db.session.add_all().

Nota:

La función db.create_all() no recrea ni actualiza una tabla si ya existe. Por ejemplo, si modificas tu modelo agregando una nueva columna y ejecutas la función db.create_all(), el cambio que hagas en el modelo no se aplicará a la tabla si la tabla ya existe en la base de datos. La solución es eliminar todas las tablas de la base de datos existentes con la función db.drop_all() y luego recrearlas con la función db.create_all(), como se muestra en el archivo init_db.py.

Este proceso aplicará las modificaciones que realices a tus modelos pero también eliminará todos los datos existentes en la base de datos. Para actualizar la base de datos y preservar los datos existentes, deberás utilizar la migración de esquema, la cual te permite modificar tus tablas y preservar datos. Puedes utilizar la extensión Flask-Migrate para realizar migraciones de esquema de SQLAlchemy a través de la interfaz de línea de comandos de Flask.

Ejecuta el programa init_db.py para aplicar cambios a la base de datos:

  1. python init_db.py

El programa debería ejecutarse exitosamente sin ningún resultado. Si ves un error, asegúrate de haber realizado correctamente los cambios en el archivo init_db.py.

Para ver las publicaciones y etiquetas que están actualmente en la base de datos, abre el shell de Flask:

  1. flask shell

Ejecuta el siguiente código Python que recorre las publicaciones y etiquetas:

from app import Post

posts = Post.query.all()

for post in posts:
    print(post.title)
    print(post.tags)
    print('---')

Aquí, importas el modelo Post desde app.py. Consultas la tabla de publicaciones y recuperas todas las publicaciones de la base de datos. Recorres las publicaciones y imprimes el título de la publicación y la lista de etiquetas asociadas con cada publicación.

Obtendrás un resultado similar al siguiente:

Output
Post The First [<Tag "animals">, <Tag "writing">] --- Post The Third [<Tag "cooking">, <Tag "tech">, <Tag "writing">] --- Post The Second [] ---

Puedes acceder a los nombres de las etiquetas utilizando tag.name como se muestra en el siguiente ejemplo, que puedes ejecutar utilizando el shell de Flask:

from app import Post

posts = Post.query.all()

for post in posts:
    print('TITLE: ', post.title)
    print('-')
    print('TAGS:')
    for tag in post.tags:
        print('> ', tag.name)
    print('-'*30)

Aquí, además de imprimir el título de la publicación, también recorres las etiquetas de cada publicación e imprimes el nombre de la etiqueta.

Obtendrás un resultado similar al siguiente:

Output
TITLE: Post The First - TAGS: > animals > writing ------------------------------ TITLE: Post The Third - TAGS: > cooking > tech > writing ------------------------------ TITLE: Post The Second - TAGS: ------------------------------

Como puedes ver, las etiquetas que añadiste a las publicaciones en el programa init_db.py están correctamente vinculadas con las publicaciones a las que se etiquetaron.

Para ver una demostración de cómo acceder a las publicaciones etiquetadas con una etiqueta específica a través de tag.posts, ejecuta el siguiente código en la consola de Flask:

from app import Tag

writing_tag = Tag.query.filter_by(name='writing').first()

for post in writing_tag.posts:
    print(post.title)
    print('-'*6)
    print(post.content)
    print('-')
    print([tag.name for tag in post.tags])
    print('-'*20)

Importas el modelo Tag. Luego utilizas el método filter_by() en el atributo query pasándole un parámetro name para obtener la etiqueta writing por su nombre, y obtienes el primer resultado usando el método first(). Almacenas el objeto de etiqueta en una variable llamada writing_tag. Para obtener más información sobre el método filter_by, consulta el tutorial Paso 4 de Cómo Usar Flask-SQLAlchemy para Interactuar con Bases de Datos en una Aplicación Flask.

Recorres las publicaciones etiquetadas con la etiqueta writing, a las que accedes a través de writing_tag.posts. Imprimes el título de la publicación, el contenido y una lista de nombres de etiquetas que construyes usando una comprensión de lista basada en las etiquetas de la publicación, a las que accedes a través de post.tags.

Obtendrás una salida similar a la siguiente:

Output
Post The Third ------ Content for the third post - ['cooking', 'tech', 'writing'] -------------------- Post The First ------ Content for the first post - ['animals', 'writing'] --------------------

Aquí puedes ver las dos publicaciones que fueron etiquetadas con la etiqueta writing, y los nombres de las etiquetas se muestran en una lista de Python.

Ahora puedes acceder a las publicaciones y sus etiquetas, así como acceder a las publicaciones de una etiqueta específica.

Has agregado un modelo de base de datos que representa la tabla de etiquetas. Has vinculado entre publicaciones y etiquetas usando una tabla de asociación, e has insertado algunas etiquetas en la base de datos y has etiquetado publicaciones con ellas. Has accedido a las publicaciones y sus etiquetas y las publicaciones de una etiqueta individual. A continuación, usarás la consola de Flask para agregar nuevas publicaciones y nuevas etiquetas y vincular entre etiquetas y publicaciones, y aprenderás cómo eliminar etiquetas de una publicación.

Paso 3 — Manejo de Datos en una Relación de Muchos a Muchos

En este paso, usarás la consola de Flask para agregar nuevas publicaciones a la base de datos, agregar etiquetas y vincular entre publicaciones y etiquetas. Accederás a publicaciones con sus etiquetas, y verás cómo disociar un elemento de otro en relaciones de Muchos a Muchos.

Primero, con tu entorno de programación activado, abre la consola de Flask si aún no lo has hecho:

  1. flask shell

Luego, agrega algunas publicaciones y etiquetas:

from app import db, Post, Tag

life_death_post = Post(title='A post on life and death', content='life and death')
joy_post = Post(title='A post on joy', content='joy')

life_tag = Tag(name='life')
death_tag = Tag(name='death')
joy_tag = Tag(name='joy')

life_death_post.tags.append(life_tag)
life_death_post.tags.append(death_tag)
joy_post.tags.append(joy_tag)

db.session.add_all([life_death_post, joy_post, life_tag, death_tag, joy_tag])

db.session.commit()

Esto crea dos publicaciones y tres etiquetas. Etiquetas las publicaciones con sus etiquetas relacionadas, y usa el método add_all() para agregar los elementos recién creados a la sesión de la base de datos. Luego, confirma los cambios y aplícalos a la base de datos usando el método commit().

A continuación, usa la consola de Flask para obtener todas las publicaciones y sus etiquetas como lo has hecho en el paso anterior:

posts = Post.query.all()

for post in posts:
    print(post.title)
    print(post.tags)
    print('---')

Obtendrás una salida similar a la siguiente:

Output
Post The First [<Tag "animals">, <Tag "writing">] --- Post The Third [<Tag "cooking">, <Tag "tech">, <Tag "writing">] --- Post The Second [] --- A post on life and death [<Tag "life">, <Tag "death">] --- A post on joy [<Tag "joy">] ---

Puedes ver que se agregaron publicaciones junto con sus etiquetas.

Para demostrar cómo romper una relación entre dos elementos en una relación de base de datos de muchos a muchos, digamos que la publicación Post The Third ya no trata sobre cocina, así que tendrás que quitar la etiqueta cooking de ella.

Primero, obtén la publicación y la etiqueta que deseas eliminar:

  1. from app import db, Post, Tag
  2. post = Post.query.filter_by(title='Post The Third').first()
  3. tag = Tag.query.filter_by(name='cooking').first()
  4. print(post.title)
  5. print(post.tags)
  6. print(tag.posts)

Aquí obtienes la publicación titulada Post The Third usando el método filter_by(). Obtienes la etiqueta cooking. Imprimes el título de la publicación, sus etiquetas y las publicaciones etiquetadas con la etiqueta cooking.

El método filter_by() devuelve un objeto de consulta, y puedes usar el método all() para obtener una lista de todos los resultados. Pero como esperamos solo un resultado en este caso, usas el método first() para obtener el primer (y único) resultado. Para más información sobre los métodos first() y all(), consulta el Paso 4 de Cómo Utilizar Flask-SQLAlchemy para Interactuar con Bases de Datos en una Aplicación Flask.

Obtendrás la siguiente salida:

Output
Post The Third [<Tag "cooking">, <Tag "tech">, <Tag "writing">] [<Post "Post The Third">]

Aquí ves el título de la publicación, las etiquetas de la publicación y una lista de las publicaciones etiquetadas con la etiqueta cooking.

Para eliminar la etiqueta cooking de la publicación, usa el método remove() de la siguiente manera:

  1. post.tags.remove(tag)
  2. db.session.commit()
  3. print(post.tags)
  4. print(tag.posts)

Aquí usas el método remove() para desasociar la etiqueta cooking de la publicación. Luego usas el método db.session.commit() para aplicar los cambios a la base de datos.

Obtendrás una salida que confirma que la etiqueta fue eliminada de la publicación:

Output
[<Tag "tech">, <Tag "writing">] []

Como puedes ver, la etiqueta cooking ya no está en la lista de post.tags, y la publicación ha sido eliminada de la lista de tag.posts.

Salir del shell de Flask:

  1. exit()

Has añadido nuevas publicaciones y etiquetas. Has etiquetado publicaciones y has eliminado etiquetas de publicaciones. A continuación, mostrarás las etiquetas de cada publicación en la página de índice de tu blog web de Flask.

Paso 4 — Mostrar Etiquetas Debajo de Cada Publicación

En este paso, editarás la plantilla de índice para mostrar las etiquetas debajo de cada publicación.

Primero, echa un vistazo a la página de inicio actual del blog web de Flask.

Con tu entorno de programación activado, dile a Flask sobre la aplicación (app.py en este caso) usando la variable de entorno FLASK_APP. Luego, establece la variable de entorno FLASK_ENV en development para ejecutar la aplicación en modo de desarrollo:

  1. export FLASK_APP=app
  2. export FLASK_ENV=development

A continuación, ejecuta la aplicación:

  1. flask run

Con el servidor de desarrollo en funcionamiento, visita la siguiente URL en tu navegador:

http://127.0.0.1:5000/

Verás una página similar a la siguiente:

Deja el servidor de desarrollo en funcionamiento y abre una nueva ventana de terminal.

Necesitarás mostrar las etiquetas de cada publicación en dos páginas: debajo de cada publicación en la página de índice y debajo del contenido de la publicación en la página de la publicación. Utilizarás el mismo código para mostrar las etiquetas. Para evitar la repetición de código, usarás una macro Jinja, que se comporta como una función de Python. Una macro contiene código HTML dinámico que se puede mostrar donde llames a la macro, y al editarla se aplican cambios dondequiera que se haya llamado, lo que hace que el código sea reutilizable.

Primero, abre un nuevo archivo llamado macros.html en tu directorio templates:

  1. nano templates/macros.html

Agrega el siguiente código a él:

flask_app/templates/macros.html
{% macro display_tags(post) %}
    <div class="tags">
        <p>
            <h4>Tags:</h4>
            {% for tag in post.tags %}
                <a href="#" style="text-decoration: none; color: #dd5b5b">
                    {{ tag.name }}
                </a>
                |
            {% endfor %}
        </p>
    </div>
{% endmacro %}

Guarda y cierra el archivo.

Aquí, usas la palabra clave macro para declarar una macro llamada display_tags() con un parámetro llamado post. Utilizas una etiqueta <div>, en la cual muestras un encabezado <h4>. Utilizas un bucle for para recorrer las etiquetas del objeto de publicación que se pasará como argumento a la macro cuando se llame, similar a cómo se pasa un argumento en una llamada a función de Python. Obtienes las etiquetas a través de post.tags. Muestras el nombre de la etiqueta dentro de una etiqueta <a>. Más tarde editarás el valor del atributo href para vincularlo a una página de etiquetas que crearás donde se mostrarán todas las publicaciones etiquetadas con una etiqueta específica. Especificas el final de la macro usando la palabra clave endmacro.

A continuación, para mostrar las etiquetas debajo de cada publicación en la página de índice, abre el archivo de plantilla index.html:

  1. nano templates/index.html

Primero, deberás importar la macro display_tags() del archivo macros.html. Agrega la siguiente importación en la parte superior, arriba de la línea {% extends 'base.html' %}:

flask_app/templates/index.html
{% from 'macros.html' import display_tags %}
{% extends 'base.html' %}

Luego, edita el bucle for post in posts, llamando a la macro display_tags() de la siguiente manera:

flask_app/templates/index.html
{% for post in posts %}
    <div class="post">
        <p><b>#{{ post.id }}</b></p>
        <b>
            <p class="title">
                <a href="{{ url_for('post', post_id=post.id)}}">
                    {{ post.title }}
                </a>
            </p>
        </b>
        <div class="content">
            <p>{{ post.content }}</p>
        </div>

        {{ display_tags(post) }}

        <hr>
    </div>
{% endfor %}

Guarda y cierra el archivo.

Llama a la macro display_tags(), pasándole el objeto post. Esto mostrará los nombres de las etiquetas debajo de cada publicación.

Actualiza la página de índice en tu navegador y verás las etiquetas debajo de cada publicación, como se muestra en la siguiente imagen:

A continuación, agregarás etiquetas debajo del contenido de la publicación en la página de la publicación. Abre el archivo de plantilla post.html:

  1. nano templates/post.html

Primero, importa la macro display_tags en la parte superior:

flask_app/templates/post.html
{% from 'macros.html' import display_tags %}
{% extends 'base.html' %}

Luego llama a la macro display_tags(), pasándole el objeto post debajo del contenido de la publicación y arriba de la etiqueta <hr>:

flask_app/templates/post.html
<div class="post">
    <p><b>#{{ post.id }}</b></p>
    <b>
        <p class="title">{{ post.title }}</p>
    </b>
    <div class="content">
        <p>{{ post.content }}</p>
    </div>

    {{ display_tags(post) }}

    <hr>
    <h3>Comments</h3>

Guarda y cierra el archivo.

Ahora, navega hasta una página de publicación:

http://127.0.0.1:5000/2

Verás que las etiquetas se muestran de la misma manera que las etiquetas mostradas en la página de índice.

Has mostrado las etiquetas que agregaste a las publicaciones debajo de cada publicación. A continuación, agregarás una nueva ruta a tu aplicación Flask que muestre todas las publicaciones etiquetadas con una etiqueta específica. Luego harás funcionales los enlaces de etiquetas que has mostrado en este paso.

Paso 5 — Mostrar Etiquetas y sus Publicaciones

En este paso, agregarás una ruta y una plantilla a tu aplicación web para mostrar las etiquetas que tienes en tu base de datos y sus publicaciones.

Primero, agregarás una ruta para mostrar las publicaciones de cada etiqueta. Por ejemplo, la ruta /tags/nombre_etiqueta/ mostrará una página que muestra todas las publicaciones etiquetadas con una etiqueta llamada nombre_etiqueta.

Abre app.py para editarlo:

  1. nano app.py

Agrega la siguiente ruta al final del archivo:

flask_app/app.py

# ...

@app.route('/tags/<tag_name>/')
def tag(tag_name):
    tag = Tag.query.filter_by(name=tag_name).first_or_404()
    return render_template('tag.html', tag=tag)

Guarda y cierra el archivo.

Aquí utilizas una variable de URL llamada nombre_etiqueta que determina la etiqueta y las publicaciones etiquetadas con ella que se mostrarán en la página de la etiqueta. El nombre de la etiqueta se pasa a la función de vista tag() a través del parámetro nombre_etiqueta, que utilizas en el método filter_by() para consultar la etiqueta. Utilizas first_or_404() para obtener el objeto de etiqueta y guardarlo en una variable llamada etiqueta, o para responder con un mensaje de error 404 No encontrado en caso de que no exista ninguna etiqueta con el nombre dado en la base de datos.

Luego renderizas un archivo de plantilla llamado tag.html, pasándole el objeto etiqueta.

Abre el nuevo templates/tag.html para editarlo:

  1. nano templates/tag.html

Agrega el siguiente código:

flask_app/templates/tag.html
{% from 'macros.html' import display_tags %}
{% extends 'base.html' %}

{% block content %}
    <span class="title">
        <h1>{% block title %} Posts Tagged with "{{ tag.name }}" {% endblock %}</h1>
    </span>
    <div class="content">
        {% for post in tag.posts %}
        <div class="post">
            <p><b>#{{ post.id }}</b></p>
            <b>
                <p class="title">
                    <a href="{{ url_for('post', post_id=post.id)}}">
                        {{ post.title }}
                    </a>
                </p>
            </b>
            <div class="content">
                <p>{{ post.content }}</p>
            </div>

            {{ display_tags(post) }}

            <hr>
        </div>
        {% endfor %}
    </div>
{% endblock %}

Guarda y cierra el archivo.

Importas la macro display_tags() desde macros.html, y extiendes la plantilla base.

En el bloque de contenido, estableces un encabezado como título con el nombre de la etiqueta incluido. Luego, iteras a través de las publicaciones etiquetadas con la etiqueta dada, a las que accedes mediante tag.posts. Muestras el ID de la publicación, el título de la publicación y el contenido de la publicación. Luego llamas a la macro display_tags() para mostrar todas las etiquetas de la publicación.

Con tu servidor de desarrollo en funcionamiento, navega a la siguiente URL:

http://127.0.0.1:5000/tags/writing/

Esta es la página para la etiqueta writing. Como puedes ver, se muestran todas las publicaciones etiquetadas con writing:

Ahora edita la macro display_tags() para hacer que los enlaces de las etiquetas sean funcionales. Abre macros.html:

  1. nano templates/macros.html

Edita el valor del atributo href de la siguiente manera:

flask_app/templates/macros.html

{% macro display_tags(post) %}
    <div class="tags">
        <p>
            <h4>Tags:</h4>
            {% for tag in post.tags %}
            <a href="{{ url_for('tag', tag_name=tag.name) }}"
               style="text-decoration: none; color: #dd5b5b">
                    {{ tag.name }}
                </a>
                |
            {% endfor %}
        </p>
    </div>
{% endmacro %}

Guarda y cierra el archivo.

Actualiza las páginas donde se ha utilizado la macro display_tags(), y verás que los enlaces de las etiquetas ahora son funcionales:

http://127.0.0.1:5000/
http://127.0.0.1:5000/2/
http://127.0.0.1:5000/tags/writing/

Como puedes ver, el uso de macros Jinja te permite reutilizar código, y editar una macro aplica cambios en múltiples plantillas.

Has agregado una página para etiquetas individuales donde los usuarios pueden ver todas las publicaciones que fueron etiquetadas con una etiqueta específica, y las etiquetas debajo de las publicaciones ahora enlazan a esta nueva página.

Conclusión

Las etiquetas que agregaste a tu sistema de blogs demuestran cómo gestionar relaciones de muchos a muchos usando la extensión Flask-SQLAlchemy. Aprendiste cómo vincular dos tablas relacionadas usando una tabla de asociación (también llamada una tabla de unión), asociar una entrada con otra, agregar la entrada a la base de datos, y acceder y disociar datos de una entrada.

Si quieres leer más sobre Flask, revisa los otros tutoriales en la serie Cómo Construir Aplicaciones Web con Flask.

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-many-to-many-database-relationships-with-flask-sqlalchemy