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 para Doações.

Introdução

O Flask é um framework web leve em Python que fornece ferramentas úteis e recursos para criar aplicações web na linguagem Python. O SQLAlchemy é um kit de ferramentas SQL que fornece acesso eficiente e de alto desempenho a bancos de dados relacionais. Ele oferece 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. 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 facilita o uso do SQLAlchemy com o Flask, fornecendo ferramentas e métodos para interagir com seu banco de dados em suas aplicações Flask por meio 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ê irá 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, ele 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 um-para-muitos entre posts e comentários em um aplicativo de blog.

Ao 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

Etapa 1 — Configurando a Aplicação Web

Nesta etapa, 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 do Flask para entender a estrutura da aplicação. Se você seguiu o tutorial na seção de pré-requisitos e ainda tiver o código e o ambiente virtual em sua máquina local, você pode pular esta etapa.

Para demonstrar a adição de um relacionamento muitos para muitos a uma aplicação web Flask com Flask-SQLAlchemy, você utilizará o código da aplicação do tutorial anterior, que é um sistema de blogs com a capacidade de adicionar e exibir postagens, comentar em 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ê o definirá como 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

Depois importe o objeto de banco de dados 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ê abrir seu navegador, terá a aplicação rodando no seguinte URL:

http://127.0.0.1:5000/

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

Se você receber um erro, verifique se seguiu corretamente os passos acima.

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

Em seguida, você passará pelos modelos de banco de dados do 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 o próximo passo.

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 tem uma coluna de ID, um título, conteúdo e um relacionamento de Um-para-Muitos com a tabela de comentários.

  • Comment: que tem 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 de índice, 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 o post em que cada comentário foi publicado.
  • /comentários/<int:id_do_comentário>/excluir: Uma rota que exclui um comentário por meio de um botão Excluir Comentário.

Feche o arquivo app.py.

No próximo passo, você usará uma relação muitos para muitos para criar um vínculo entre duas tabelas.

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

Neste passo, você adicionará um modelo de banco de dados que representará a tabela de tags. Você o vinculará com a tabela de posts existente usando uma tabela de associação, que é uma tabela que conecta suas duas tabelas em uma relação muitos para muitos. Uma relação muitos para muitos vincula duas tabelas onde cada item em uma tabela possui 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 no 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 assim:

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

Vamos supor 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 da seguinte maneira:

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

Esta 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 referencie as 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 faz a ligação entre 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 com a tag com o ID 1 (vida). Na segunda linha, a mesma postagem também está relacionada com a tag com o ID 2 (morte). Isso significa que a postagem está marcada com as tags vida e morte. Da mesma forma, você pode marcar cada postagem com várias tags.

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

Primeiro, abra o 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 tem 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 tem duas colunas:

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

Você usa o nome da tag no método especial __repr__() para dar 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ê utiliza 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 uma relação de muitos para muitos entre posts e tags.

Você utiliza o parâmetro backref para adicionar uma referência de retorno 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 ficar 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)  # Tag o primeiro post com 'animais'
post1.tags.append(tag4)  # Tag o primeiro post com 'escrita'
post3.tags.append(tag3)  # Tag o terceiro post com 'culinária'
post3.tags.append(tag2)  # Tag o terceiro post com 'tecnologia'
post3.tags.append(tag4)  # Tag 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, adiciona tags às postagens usando o atributo tags que foi adicionado via 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 do Python.

Em seguida, adiciona as tags que você 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 de banco de dados existentes 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 êxito sem nenhuma saída. Se você ver um erro, certifique-se de ter feito 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 percorre as 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ê percorre as 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 conforme 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 percorre as 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 você pode ver, as tags que você adicionou às postagens no programa init_db.py estão devidamente vinculadas às postagens com as quais foram marcadas.

Para ver uma demonstração de como acessar postagens marcadas com uma tag específica via 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, você usa o método filter_by() no atributo query passando um parâmetro name para obter a tag writing pelo seu nome, e você obtém o primeiro resultado usando o método first(). Você armazena o objeto de tag em uma variável chamada writing_tag. Para mais informações sobre o método filter_by, consulte Passo 4 do tutorial Como Usar o Flask-SQLAlchemy para Interagir com Bancos de Dados em uma Aplicação Flask.

Você percorre as postagens marcadas com a tag writing, que você acessa via writing_tag.posts. Você imprime o título da postagem, o conteúdo e uma lista de nomes de tag que você constrói usando uma compreensão de lista com base nas tags da postagem, que você acessa via post.tags.

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

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

Aqui você vê as duas postagens que foram marcadas com a tag writing, e os nomes das tags são exibidos em uma lista Python.

Agora você pode acessar postagens e suas tags e acessar postagens de uma tag 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 as aplica 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 você 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 juntamente com suas tags.

Para demonstrar como quebrar um relacionamento entre dois itens em uma relação de banco de dados muitos para muitos, digamos que a postagem Post The Third não está mais relacionada à culinária, então você terá que remover a tag cooking dela.

Primeiro, obtenha a postagem 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 a postagem intitulada Post The Third usando o método filter_by(). Você obtém a tag cooking. Você imprime o título da postagem, suas tags e as postagens marcadas 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 o 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 da postagem, as tags da postagem e uma lista das postagens marcadas com a tag cooking.

Para remover a tag cooking da postagem, 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 da postagem. Então 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 de post.tags e a postagem foi removida da lista de 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 inicial do seu blog web Flask.

Passo 4 — Exibindo Tags Sob Cada Postagem

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

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

Com seu ambiente de programação ativado, informe ao Flask sobre a aplicação (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 a aplicação no modo de desenvolvimento:

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

Em seguida, execute a aplicação:

  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 de terminal.

Você precisará exibir as tags de cada post em duas páginas: abaixo de cada post na página de índice e abaixo do conteúdo do post na página do post. 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 editar aplicará alterações onde quer que ela 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 post que será passado como argumento para a macro ao chamá-la, semelhante à passagem de um argumento 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>. Mais tarde, você editará o valor do atributo href para vincular a uma página de tag que você criará, onde todos os posts marcados com uma tag específica serão exibidos. Você especifica o final da macro usando a palavra-chave endmacro.

Em seguida, para exibir as tags abaixo de cada post 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 forma:

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 irá exibir os nomes das tags sob cada postagem.

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

Em seguida, você irá 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' %}

Em seguida, 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 forma que as tags exibidas na página de índice.

Você exibiu as tags que adicionou às postagens sob cada postagem. Em seguida, você irá adicionar uma nova rota à sua aplicação Flask que exibirá todas as postagens marcadas com uma tag específica. Em seguida, você fará com que os links das tags que você exibiu neste passo sejam funcionais.

Passo 5 — Exibindo Tags e Suas Postagens

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

Primeiro, você irá adicionar uma rota para exibir as postagens de cada tag. Por exemplo, a rota /tags/nome_da_tag/ irá 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ê usa 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-lhe o objeto tag.

Abra o novo arquivo 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 a macro display_tags() do arquivo macros.html e estende o template base.

No bloco de conteúdo, você define um título como um cabeçalho com o nome da tag incluído. Em seguida, itera pelos posts marcados com a tag fornecida, que você acessa através de tag.posts. Você exibe o ID do post, o título do post e o conteúdo do post. Em seguida, chama a macro display_tags() para exibir todas as tags dos posts.

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

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

Esta é a página para a tag writing. Como você pode ver, todos os posts marcados com writing estão sendo exibidos:

Agora, edite a 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 a macro display_tags() foi usada 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, usar macros Jinja permite reutilizar código e editar uma macro aplica alterações em vários templates.

Você adicionou uma página para tags individuais onde os usuários podem visualizar todos os posts marcados com uma tag específica, e as tags abaixo dos posts agora estão vinculadas a esta nova página.

Conclusão

As tags 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 desassociar dados de uma entrada.

Se você quiser ler mais sobre o Flask, confira 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