Como usar relacionamentos de banco de dados muitos para muitos com Flask-SQLAlchemy

O autor selecionou o Fundo Livre e de Código Aberto para receber uma doação como parte do programa Escreva por Doações.

Introdução

O Flask é um framework web leve em Python que fornece ferramentas e recursos úteis para criar aplicações web na linguagem Python. O SQLAlchemy é um kit de ferramentas SQL que fornece acesso eficiente e de alto desempenho ao banco de dados para bancos de dados relacionais. Ele fornece maneiras de interagir com vários motores de banco de dados, como SQLite, MySQL e PostgreSQL. Ele dá acesso às funcionalidades SQL do banco de dados. Ele também fornece um Mapeador Relacional de Objetos (ORM), que permite fazer consultas e manipular dados usando objetos e métodos Python. O Flask-SQLAlchemy é uma extensão do Flask que torna o uso do SQLAlchemy com o Flask mais fácil, fornecendo ferramentas e métodos para interagir com seu banco de dados em suas aplicações Flask através do 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.

Neste tutorial, você vai modificar uma aplicação construída usando Flask e Flask-SQLAlchemy, adicionando uma relação muitos para muitos a ela. Você terá uma relação entre postagens e tags, onde cada postagem de blog pode ter várias tags, e cada tag pode ter várias postagens marcadas com ela.

Embora você possa seguir este tutorial de forma independente, também é uma continuação do tutorial Como Usar Relacionamentos de Banco de Dados Um-para-Muitos com Flask-SQLAlchemy, no qual você constrói um banco de dados de várias tabelas com um relacionamento de um-para-muitos entre postagens e comentários em um aplicativo de blog.

No final do tutorial, seu aplicativo terá um novo recurso para adicionar tags às postagens. As postagens podem ser marcadas com várias tags, e cada página de tag exibirá todas as postagens marcadas com ela.

Pré-requisitos

Passo 1 — Configurando a Aplicação Web

Neste passo, você configurará a aplicação de blog para estar pronta para modificações. Você também revisará os modelos de banco de dados Flask-SQLAlchemy e as rotas Flask para entender a estrutura da aplicação. Se você seguiu o tutorial na seção de pré-requisitos e ainda possui o código e o ambiente virtual em sua máquina local, pode pular este passo.

Para demonstrar a adição de um relacionamento muitos para muitos a uma aplicação web Flask com Flask-SQLAlchemy, você usará o código de aplicação do tutorial anterior, que é um sistema de blogs com a capacidade de adicionar e exibir postagens, comentar postagens e ler e excluir comentários existentes.

Clone o repositório e renomeie-o de flask-slqa-bloggy para flask_app com o seguinte comando:

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

Navegue até flask_app:

  1. cd flask_app

Em seguida, crie um novo ambiente virtual:

  1. python -m venv env

Ative o ambiente:

  1. source env/bin/activate

Instale o Flask e o Flask-SQLAlchemy:

  1. pip install Flask Flask-SQLAlchemy

Depois, defina as seguintes variáveis de ambiente:

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

FLASK_APP indica a aplicação que você está desenvolvendo atualmente, que é app.py neste caso. FLASK_ENV especifica o modo. Você vai definir para development para o modo de desenvolvimento; isso permitirá que você depure a aplicação. Lembre-se de não usar este modo em um ambiente de produção.

Em seguida, abra o shell do Flask para criar as tabelas do banco de dados:

  1. flask shell

Em seguida, importe o objeto de banco de dados db do Flask-SQLAlchemy, o modelo Post e o modelo Comment, e crie as tabelas do banco de dados usando a função db.create_all():

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

Em seguida, popule o banco de dados usando o programa init_db.py:

  1. python init_db.py

Isso adiciona três postagens e quatro comentários ao banco de dados.

Execute o servidor de desenvolvimento:

  1. flask run

Se você acessar o seu navegador, terá a aplicação em execução no seguinte URL:

http://127.0.0.1:5000/

Você verá uma página semelhante à seguinte:

Se você receber um erro, certifique-se de ter seguido corretamente as etapas acima.

Para parar o servidor de desenvolvimento, use CTRL + C.

Em seguida, você passará pelos modelos de banco de dados Flask-SQLAlchemy para entender as relações atuais entre as tabelas. Se estiver familiarizado com o conteúdo do arquivo app.py, você pode pular para a próxima etapa.

Abra o arquivo app.py:

  1. nano app.py

O conteúdo do arquivo é o seguinte:

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))

Aqui, você tem dois modelos de banco de dados representando duas tabelas:

  • Post: que possui uma coluna de ID, um título, conteúdo e uma relação de Um-para-Muitos com a tabela de comentários.

  • Comment: que possui uma coluna de ID, uma coluna para conteúdo e uma coluna post_id para referenciar o post ao qual o comentário pertence.

Abaixo dos modelos, você tem as seguintes rotas:

  • /: A página inicial, que exibe todos os posts no banco de dados.
  • /<int:post_id>/: A página de post individual. Por exemplo, o link http://127.0.0.1:5000/2/ exibe os detalhes do segundo post no banco de dados e seus comentários.
  • /comentários/: Uma página que exibe todos os comentários no banco de dados e links para a postagem em que cada comentário foi feito.
  • /comentários/<int:id_do_comentário>/apagar: Uma rota que apaga um comentário através de um botão Apagar Comentário.

Feche o arquivo app.py.

No próximo passo, você utilizará uma relação de muitos para muitos para criar um link entre duas tabelas.

Passo 2 — Configurando Modelos de Banco de Dados para uma Relação de Muitos para Muitos

Neste passo, você adicionará um modelo de banco de dados que representará a tabela de tags. Você a vinculará com a tabela de postagens existente usando uma tabela de associação, que é uma tabela que conecta suas duas tabelas em uma relação de muitos para muitos. Uma relação de muitos para muitos vincula duas tabelas onde cada item em uma tabela tem muitos itens relacionados na outra tabela. Na tabela de associação, cada postagem fará referência às suas tags e cada tag fará referência às postagens marcadas com ela. Você também inserirá algumas postagens e tags em seu banco de dados, imprimirá postagens com suas tags e imprimirá tags e suas postagens relacionadas.

Vamos supor que você tenha uma tabela simples para postagens de blog da seguinte forma:

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

E uma tabela para tags como segue:

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

Vamos dizer que você queira marcar Uma postagem sobre vida e morte com as tags vida e morte. Você pode fazer isso adicionando uma nova linha na tabela de postagens assim:

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

Essa abordagem não funciona, porque cada coluna deve ter apenas um valor. Se você tiver múltiplos valores, operações básicas como adicionar e atualizar dados se tornam complicadas e lentas. Em vez disso, deve haver uma terceira tabela que faça referência às chaves primárias das tabelas relacionadas—essa tabela é frequentemente chamada de uma tabela de associação ou uma tabela de junção, e ela armazena IDs de cada item de cada tabela.

Aqui está um exemplo de uma tabela de associação que liga postagens e tags:

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

Na primeira linha, a postagem com o ID 1 (ou seja, Uma postagem sobre vida e morte) está relacionada à tag com o ID 1 (vida). Na segunda linha, a mesma postagem também está relacionada à tag com o ID 2 (morte). Isso significa que a postagem está marcada com ambas as tags vida e morte. Da mesma forma, você pode marcar cada postagem com múltiplas tags.

Agora, você vai modificar o arquivo app.py para adicionar um novo modelo de banco de dados que representa a tabela que você usará para armazenar tags. Você também adicionará uma tabela de associação chamada post_tag que vincula postagens a tags.

Primeiro, abra app.py para estabelecer uma relação entre postagens e tags:

  1. nano app.py

Adicione uma tabela post_tag e um modelo Tag abaixo do objeto db e acima do modelo Post, em seguida, adicione um pseudo-coluna tags ao modelo Post para que você possa acessar as tags de uma postagem via post.tags e acessar as postagens de uma tag via 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}">'

Salve e feche o arquivo.

Aqui você usa a função db.Table() para criar uma tabela com duas colunas. Para tabelas de associação, a melhor prática é usar uma tabela em vez de um modelo de banco de dados.

A tabela post_tag possui duas colunas representando duas chaves estrangeiras, que são chaves usadas para referenciar colunas de chave primária em outra tabela:

  • post_id: Uma chave estrangeira inteira que representa o ID da postagem e referencia a coluna ID na tabela post.
  • tag_id: Uma chave estrangeira inteira que representa o ID da tag e referencia a coluna ID na tabela tag.

Essas chaves estabelecem as relações entre as tabelas.

Abaixo da tabela post_tag, você cria um modelo Tag, que representa a tabela na qual você irá armazenar suas tags. Esta tabela de tags possui duas colunas:

  • id: O ID da tag.
  • name: O nome da tag.

Você usa o nome da tag no método especial __repr__() para fornecer a cada objeto de tag uma representação de string clara para fins de depuração.

Você adiciona uma variável de classe tags ao modelo Post. Você usa o método db.relationship(), passando o nome do modelo de tags (Tag neste caso).

Você passa a tabela de associação post_tag para o parâmetro secondary para estabelecer um relacionamento muitos para muitos entre posts e tags.

Você usa o parâmetro backref para adicionar uma referência inversa que se comporta como uma coluna ao modelo Tag. Dessa forma, você pode acessar os posts da tag via tag.posts e as tags de um post via post.tags. Você verá um exemplo demonstrando isso posteriormente.

Em seguida, edite o programa Python init_db.py para modificar o banco de dados adicionando a tabela de associação post_tag e a tabela de tags que será baseada no modelo Tag:

  1. nano init_db.py

Edite o arquivo para parecer como segue:

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)  # Marque o primeiro post com 'animais'
post1.tags.append(tag4)  # Marque o primeiro post com 'escrita'
post3.tags.append(tag3)  # Marque o terceiro post com 'culinária'
post3.tags.append(tag2)  # Marque o terceiro post com 'tecnologia'
post3.tags.append(tag4)  # Marque o terceiro post com 'escrita'


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()

Salve e feche o arquivo.

Aqui, você importa o modelo Tag. Você exclui tudo no banco de dados usando a função db.drop_all() para adicionar as tabelas de tags e post_tag com segurança e evitar quaisquer problemas comuns relacionados à adição de novas tabelas a um banco de dados. Em seguida, você cria todas as tabelas novamente usando a função db.create_all().

Após o código do tutorial anterior declarando as postagens e comentários, você usa o modelo Tag para criar quatro tags.

Em seguida, você adiciona tags às postagens usando o atributo tags que foi adicionado através da linha tags = db.relationship('Tag', secondary=post_tag, backref='posts') no arquivo app.py. Você atribui tags às postagens usando um método append() semelhante às listas em Python.

Em seguida, você adiciona as tags que criou à sessão do banco de dados usando a função db.session.add_all().

Nota:

A função db.create_all() não recria ou atualiza uma tabela se ela já existir. Por exemplo, se você modificar seu modelo adicionando uma nova coluna e executar a função db.create_all(), a alteração feita no modelo não será aplicada à tabela se a tabela já existir no banco de dados. A solução é excluir todas as tabelas existentes no banco de dados com a função db.drop_all() e depois recriá-las com a função db.create_all(), como demonstrado no arquivo init_db.py.

Este processo aplicará as modificações que você fizer aos seus modelos, mas também excluirá todos os dados existentes no banco de dados. Para atualizar o banco de dados e preservar os dados existentes, você precisará usar a migração de esquema, que permite modificar suas tabelas e preservar os dados. Você pode usar a extensão Flask-Migrate para realizar migrações de esquema do SQLAlchemy através da interface de linha de comando do Flask.

Execute o programa init_db.py para aplicar as alterações ao banco de dados:

  1. python init_db.py

O programa deve ser executado com sucesso sem qualquer saída. Se você encontrar um erro, verifique se fez as alterações corretamente no arquivo init_db.py.

Para dar uma olhada nas postagens e tags que estão atualmente no banco de dados, abra o shell do Flask:

  1. flask shell

Execute o seguinte código Python que faz um loop pelas postagens e tags:

from app import Post

posts = Post.query.all()

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

Aqui, você importa o modelo Post de app.py. Você consulta a tabela de postagens e busca todas as postagens no banco de dados. Você faz um loop pelas postagens e imprime o título da postagem e a lista de tags associadas a cada postagem.

Você obterá uma saída semelhante à seguinte:

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

Você pode acessar os nomes das tags usando tag.name como demonstrado no exemplo a seguir, que você pode executar usando o shell do 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)

Aqui, além de imprimir o título da postagem, você também faz um loop pelas tags de cada postagem e imprime o nome da tag.

Você obterá uma saída semelhante à seguinte:

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

Como pode ver, as etiquetas que adicionou às publicações no programa init_db.py estão devidamente ligadas às publicações às quais foram etiquetadas.

Para ver uma demonstração de como aceder às publicações etiquetadas com uma etiqueta específica através de tag.posts, execute o seguinte código no shell do 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)

Você importa o modelo Tag. Em seguida, utiliza o método filter_by() no atributo query passando-lhe um parâmetro name para obter a etiqueta writing pelo seu nome, e obtém o primeiro resultado utilizando o método first(). Armazena o objeto da etiqueta numa variável chamada writing_tag. Para mais informações sobre o método filter_by, consulte o Passo 4 do Tutorial Como Utilizar o Flask-SQLAlchemy para Interagir com Bancos de Dados numa Aplicação Flask.

Você percorre as publicações etiquetadas com a etiqueta writing, às quais acede através de writing_tag.posts. Imprime o título da publicação, o conteúdo e uma lista de nomes de etiquetas que constrói utilizando uma compreensão de lista baseada nas etiquetas da publicação, às quais acede através de post.tags.

Obterá uma saída semelhante ao seguinte:

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

Aqui vê as duas publicações que foram etiquetadas com a etiqueta writing, e os nomes das etiquetas são exibidos numa lista Python.

Agora pode aceder às publicações e suas etiquetas e aceder às publicações de uma etiqueta específica.

Você adicionou um modelo de banco de dados que representa a tabela de tags. Você vinculou entre postagens e tags usando uma tabela de associação e inseriu algumas tags no banco de dados e marcou postagens com elas. Você acessou postagens e suas tags e as postagens de uma tag individual. Em seguida, você usará o shell do Flask para adicionar novas postagens e novas tags e vincular entre tags e postagens, e aprenderá como remover tags de uma postagem.

Passo 3 — Gerenciando Dados em um Relacionamento Muitos-para-Muitos

Neste passo, você usará o shell do Flask para adicionar novas postagens ao banco de dados, adicionar tags e vincular entre postagens e tags. Você acessará postagens com suas tags, e verá como desassociar um item de outro em relacionamentos Muitos-para-Muitos.

Primeiro, com seu ambiente de programação ativado, abra o shell do Flask se ainda não o tiver feito:

  1. flask shell

Em seguida, adicione algumas postagens e tags:

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()

Isso cria duas postagens e três tags. Você marca postagens com suas tags relacionadas e usa o método add_all() para adicionar os itens recém-criados à sessão do banco de dados. Em seguida, você confirma as alterações e aplica-as ao banco de dados usando o método commit().

Em seguida, use o shell do Flask para obter todas as postagens e suas tags como fez no passo anterior:

posts = Post.query.all()

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

Você obterá uma saída semelhante à seguinte:

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">] ---

Você pode ver que as postagens foram adicionadas junto com suas tags.

Para demonstrar como quebrar uma relação entre dois itens em uma relação de banco de dados muitos para muitos, digamos que o post Post The Third não é mais sobre culinária, então você terá que remover a tag cooking dele.

Primeiro, obtenha o post e a tag que deseja remover:

  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)

Aqui você busca o post intitulado Post The Third usando o método filter_by(). Você obtém a tag cooking. Você imprime o título do post, suas tags e os posts marcados com a tag cooking.

O método filter_by() retorna um objeto de consulta, e você pode usar o método all() para obter uma lista de todos os resultados. Mas como esperamos apenas um resultado neste caso, você usa o método first() para obter o primeiro (e único) resultado. Para mais informações sobre os métodos first() e all(), confira Passo 4 de Como Usar Flask-SQLAlchemy para Interagir com Bancos de Dados em uma Aplicação Flask.

Você obterá a seguinte saída:

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

Aqui você vê o título do post, as tags do post e uma lista dos posts marcados com a tag cooking.

Para remover a tag cooking do post, use o método remove() assim:

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

Aqui você usa o método remove() para desassociar a tag cooking do post. Em seguida, você usa o método db.session.commit() para aplicar as alterações ao banco de dados.

Você receberá uma saída que confirma que a tag foi removida da postagem:

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

Como você pode ver, a tag cooking não está mais na lista post.tags e a postagem foi removida da lista tag.posts.

Saia do shell do Flask:

  1. exit()

Você adicionou novas postagens e tags. Você marcou postagens e removeu tags das postagens. Em seguida, você exibirá as tags de cada postagem na página de índice do seu blog web Flask.

Passo 4 — Exibindo Tags Sob Cada Postagem

Neste passo, você irá editar o modelo de índice para exibir tags sob cada postagem.

Primeiro, dê uma olhada na página atual da página inicial do blog web Flask.

Com seu ambiente de programação ativado, informe ao Flask sobre o aplicativo (app.py neste caso) usando a variável de ambiente FLASK_APP. Em seguida, defina a variável de ambiente FLASK_ENV para development para executar o aplicativo no modo de desenvolvimento:

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

Em seguida, execute o aplicativo:

  1. flask run

Com o servidor de desenvolvimento em execução, visite a seguinte URL em seu navegador:

http://127.0.0.1:5000/

Você verá uma página semelhante à seguinte:

Deixe o servidor de desenvolvimento em execução e abra uma nova janela do terminal.

Você precisará exibir as tags de cada postagem em duas páginas: abaixo de cada postagem na página de índice e abaixo do conteúdo da postagem na página da postagem. Você usará o mesmo código para exibir as tags. Para evitar repetição de código, você usará uma macro Jinja, que se comporta como uma função Python. Uma macro contém código HTML dinâmico que pode ser exibido onde quer que você chame a macro, e a edição dela aplica alterações onde quer que tenha sido chamada, o que torna o código reutilizável.

Primeiro, abra um novo arquivo chamado macros.html no seu diretório templates:

  1. nano templates/macros.html

Adicione o seguinte código a ele:

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 %}

Salve e feche o arquivo.

Aqui, você usa a palavra-chave macro para declarar uma macro chamada display_tags() com um parâmetro chamado post. Você usa uma tag <div>, na qual exibe um cabeçalho <h4>. Você usa um loop for para percorrer as tags do objeto de postagem que será passado como argumento para a macro quando ela for chamada, semelhante à forma como um argumento é passado em uma chamada de função Python. Você obtém as tags via post.tags. Você exibe o nome da tag dentro de uma tag <a>. Posteriormente, você editará o valor do atributo href para vincular a uma página de tag que você criará, onde todas as postagens marcadas com uma tag específica serão exibidas. Você especifica o fim da macro usando a palavra-chave endmacro.

Em seguida, para exibir as tags sob cada postagem na página de índice, abra o arquivo de modelo index.html:

  1. nano templates/index.html

Primeiro, você precisará importar o macro display_tags() do arquivo macros.html. Adicione a seguinte importação no topo, acima da linha {% extends 'base.html' %}:

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

Em seguida, edite o loop for post in posts, chamando o macro display_tags() da seguinte maneira:

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 %}

Salve e feche o arquivo.

Você chama o macro display_tags(), passando o objeto post. Isso exibirá os nomes das tags embaixo de cada postagem.

Atualize a página de índice no seu navegador e você verá as tags embaixo de cada postagem, como mostrado na seguinte imagem:

Em seguida, você adicionará tags sob o conteúdo da postagem na página da postagem. Abra o arquivo de modelo post.html:

  1. nano templates/post.html

Primeiro, importe o macro display_tags no topo:

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

Então chame o macro display_tags(), passando o objeto post abaixo do conteúdo da postagem e acima da tag <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>

Salve e feche o arquivo.

Agora, navegue até uma página de postagem:

http://127.0.0.1:5000/2

Você verá que as tags são exibidas da mesma maneira que as tags exibidas na página de índice.

Você exibiu as tags que adicionou às postagens embaixo de cada postagem. Em seguida, você adicionará uma nova rota à sua aplicação Flask que exibirá todas as postagens marcadas com uma tag específica. Em seguida, você tornará os links das tags que você exibiu nesta etapa funcionais.

Passo 5 — Exibindo Tags e Suas Postagens

Neste passo, você adicionará uma rota e um modelo à sua aplicação web para exibir as tags que você tem em seu banco de dados e suas postagens.

Primeiro, você adicionará uma rota para exibir as postagens de cada tag. Por exemplo, a rota /tags/nome_da_tag/ mostrará uma página que exibe todas as postagens marcadas com uma tag chamada nome_da_tag.

Abra o arquivo app.py para edição:

  1. nano app.py

Adicione a seguinte rota ao final do arquivo:

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)

Salve e feche o arquivo.

Aqui você utiliza uma variável de URL chamada nome_da_tag que determina a tag e as postagens marcadas com ela que serão exibidas na página da tag. O nome da tag é passado para a função de visualização tag() via parâmetro nome_da_tag, que você utiliza no método filter_by() para consultar a tag. Você utiliza o first_or_404() para obter o objeto de tag e armazená-lo em uma variável chamada tag, ou para responder com uma mensagem de erro 404 Not Found caso nenhuma tag com o nome fornecido exista no banco de dados.

Então você renderiza um arquivo de modelo chamado tag.html, passando-o o objeto tag.

Abra o novo templates/tag.html para edição:

  1. nano templates/tag.html

Adicione o seguinte código a ele:

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 %}

Salve e feche o arquivo.

Você importa o macro display_tags() de macros.html, e estende o template base.

No bloco de conteúdo, você define um cabeçalho como título com o nome da tag incluído. Em seguida, você percorre as postagens marcadas com a tag fornecida, às quais você acessa via tag.posts. Você exibe o ID da postagem, o título da postagem e o conteúdo da postagem. Depois, você chama o macro display_tags() para exibir todas as tags da postagem.

Com seu servidor de desenvolvimento em execução, navegue até o seguinte URL:

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

Esta é a página para a tag writing. Como você pode ver, todas as postagens marcadas com writing estão sendo exibidas:

Agora edite o macro display_tags() para tornar os links das tags funcionais. Abra o arquivo macros.html:

  1. nano templates/macros.html

Edite o valor do atributo href da seguinte forma:

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 %}

Salve e feche o arquivo.

Atualize as páginas onde o macro display_tags() foi usado e você verá que os links das tags agora estão funcionais:

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

Como você pode ver, o uso de macros Jinja permite reutilizar código, e editar um macro aplica alterações em vários templates.

Você adicionou uma página para tags individuais onde os usuários podem ver todas as postagens marcadas com uma tag específica, e as tags sob as postagens agora estão vinculadas a esta nova página.

Conclusão

As etiquetas que você adicionou ao seu sistema de blogs demonstram como gerenciar relacionamentos muitos para muitos usando a extensão Flask-SQLAlchemy. Você aprendeu como vincular duas tabelas relacionadas usando uma tabela de associação (também chamada de tabela de junção), associar uma entrada com outra, adicionar a entrada ao banco de dados e acessar e dissociar dados de uma entrada.

Se você gostaria de ler mais sobre o Flask, confira os outros tutoriais na série Como Construir Aplicações Web com Flask.

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