Как использовать многие ко многим отношения баз данных с Flask-SQLAlchemy

Автор выбрал Фонд свободного и открытого исходного кода для получения пожертвования в рамках программы Write for DOnations.

Введение

Flask – это легкий веб-фреймворк Python, который предоставляет полезные инструменты и функции для создания веб-приложений на языке Python. SQLAlchemy – это набор инструментов для работы с SQL, который обеспечивает эффективный и высокопроизводительный доступ к базам данных. Он предоставляет способы взаимодействия с несколькими базами данных, такими как SQLite, MySQL и PostgreSQL. Он дает вам доступ к SQL-функциям базы данных. Кроме того, он предоставляет объектно-реляционный маппер (ORM), который позволяет выполнять запросы и обрабатывать данные с помощью объектов и методов Python. Flask-SQLAlchemy – это расширение Flask, которое упрощает использование SQLAlchemy с Flask, предоставляя вам инструменты и методы для взаимодействия с вашей базой данных в ваших приложениях Flask через 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.

В этом руководстве вы модифицируете приложение, созданное с использованием Flask и Flask-SQLAlchemy, добавив к нему отношение многие ко многим. У вас будет отношение между сообщениями и метками, где каждое сообщение блога может иметь несколько меток, и каждая метка может иметь несколько сообщений, отмеченных этой меткой.

Хотя вы можете следовать этому руководству независимо, это также продолжение учебного пособия Как использовать отношения один ко многим в базе данных с помощью Flask-SQLAlchemy, в котором вы создаете многостоловую базу данных с одним ко многим отношением между сообщениями и комментариями в приложении для блогования.

К концу учебного пособия ваше приложение будет иметь новую функцию добавления тегов к сообщениям. Сообщения могут быть помечены несколькими тегами, и на каждой странице тега будут отображаться все сообщения с этим тегом.

Предварительные требования

Шаг 1 — Настройка веб-приложения

На этом этапе вы настроите веб-приложение для последующей модификации. Вы также изучите модели базы данных Flask-SQLAlchemy и маршруты Flask, чтобы понять структуру приложения. Если вы следовали руководству в разделе предварительных требований и по-прежнему имеете код и виртуальное окружение на своем локальном компьютере, вы можете пропустить этот шаг.

Для демонстрации добавления отношения многие-ко-многим в веб-приложение Flask с использованием Flask-SQLAlchemy вы будете использовать код предыдущего урока, который представляет собой систему блогов с возможностью добавления и отображения сообщений, комментирования сообщений, чтения и удаления существующих комментариев.

Клонируйте репозиторий и переименуйте его из flask-slqa-bloggy в flask_app следующей командой:

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

Перейдите в flask_app:

  1. cd flask_app

Затем создайте новую виртуальную среду:

  1. python -m venv env

Активируйте среду:

  1. source env/bin/activate

Установите Flask и Flask-SQLAlchemy:

  1. pip install Flask Flask-SQLAlchemy

Затем установите следующие переменные среды:

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

FLASK_APP указывает на приложение, которое вы разрабатываете, в данном случае это app.py. FLASK_ENV определяет режим. Вы установите его в development для режима разработки; это позволит вам отлаживать приложение. Помните, что не следует использовать этот режим в производственной среде.

Затем откройте оболочку Flask, чтобы создать таблицы базы данных:

  1. flask shell

Затем импортируйте объект базы данных Flask-SQLAlchemy db, модель Post и модель Comment, и создайте таблицы базы данных с помощью функции db.create_all():

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

Затем заполните базу данных, используя программу init_db.py:

  1. python init_db.py

Это добавляет три сообщения и четыре комментария в базу данных.

Запустите сервер разработки:

  1. flask run

Если вы перейдете в браузер, приложение будет работать по следующему URL:

http://127.0.0.1:5000/

Вы увидите страницу, аналогичную следующей:

Если вы получили ошибку, убедитесь, что вы правильно следовали вышеуказанным шагам.

Чтобы остановить сервер разработки, используйте CTRL + C.

Затем вы ознакомитесь с моделями базы данных Flask-SQLAlchemy, чтобы понять текущие отношения между таблицами. Если вы знакомы с содержимым файла app.py, вы можете перейти к следующему шагу.

Откройте файл app.py:

  1. nano app.py

Содержание файла следующее:

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

Здесь у вас есть две модели базы данных, представляющие две таблицы:

  • Post: которая имеет столбец ID, заголовок, содержание и отношение “один ко многим” с таблицей комментариев.

  • Comment: которая имеет столбец ID, столбец для содержания и столбец post_id для ссылки на пост, к которому относится комментарий.

Под моделями у вас есть следующие маршруты:

  • /: Страница индекса, которая отображает все посты в базе данных.
  • /<int:post_id>/: Страница отдельного поста. Например, ссылка http://127.0.0.1:5000/2/ отображает детали второго поста в базе данных и его комментарии.
  • <код>/комментарии/: Страница, которая отображает все комментарии в базе данных и ссылки на сообщение, к которому был добавлен каждый комментарий.
  • <код>/комментарии/<int:идентификатор_комментария>/удалить: Маршрут, который удаляет комментарий через кнопку Удалить комментарий.

Закройте файл <код>app.py.

На следующем этапе вы будете использовать отношение многие-ко-многим для создания связи между двумя таблицами.

Шаг 2 — Настройка моделей базы данных для отношения многие-ко-многим

На этом этапе вы добавите модель базы данных, которая будет представлять таблицу тегов. Вы свяжете ее с существующей таблицей сообщений, используя таблицу ассоциаций, которая является таблицей, соединяющей ваши две таблицы в отношении многие-ко-многим. Отношение многие-ко-многим связывает две таблицы, где каждый элемент в одной таблице имеет много связанных элементов в другой таблице. В таблице ассоциаций каждое сообщение будет ссылаться на свои теги, и каждый тег будет ссылаться на сообщения, помеченные им. Вы также вставите несколько сообщений и тегов в вашу базу данных, распечатаете сообщения с их тегами и распечатаете теги и связанные с ними сообщения.

Предположим, у вас есть простая таблица для блог-постов следующим образом:

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

И таблица для тегов, как показано:

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

Предположим, вы хотите пометить Пост о жизни и смерти тегами жизнь и смерть. Вы можете сделать это, добавив новую строку в таблицу постов следующим образом:

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

Этот подход не работает, потому что каждый столбец должен иметь только одно значение. Если у вас есть несколько значений, основные операции, такие как добавление и обновление данных, становятся неудобными и медленными. Вместо этого должна быть третья таблица, которая ссылается на первичные ключи связанных таблиц – эта таблица часто называется таблицей ассоциаций или таблицей объединений, и она хранит идентификаторы каждого элемента из каждой таблицы.

Вот пример таблицы ассоциаций, связывающей посты и теги:

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

В первой строке пост с идентификатором 1 (то есть Пост о жизни и смерти) связан с тегом с идентификатором 1 (жизнь). Во второй строке тот же пост также связан с тегом с идентификатором 2 (смерть). Это означает, что пост помечен как тегами жизнь и смерть. Аналогично можно помечать каждый пост несколькими тегами.

Теперь вы измените файл app.py, чтобы добавить новую модель базы данных, представляющую таблицу, которую вы будете использовать для хранения тегов. Вы также добавите таблицу ассоциаций с названием post_tag, которая связывает посты с тегами.

Сначала откройте app.py, чтобы установить отношение между постами и тегами:

  1. nano app.py

Добавьте таблицу post_tag и модель Tag ниже объекта db и выше модели Post, затем добавьте псевдо-столбец отношения tags к модели Post, чтобы вы могли получать доступ к тегам сообщений через post.tags и получать доступ к сообщениям тега через 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}">'

Сохраните и закройте файл.

Здесь вы используете функцию db.Table() для создания таблицы с двумя столбцами. Для таблиц ассоциаций лучшей практикой является использование таблицы вместо модели базы данных.

Таблица post_tag имеет два столбца, представляющих два внешних ключа, которые используются для ссылки на столбцы первичных ключей в другой таблице:

  • post_id: Целочисленный внешний ключ, представляющий идентификатор сообщения и ссылается на столбец ID в таблице post.
  • tag_id: Целочисленный внешний ключ, представляющий идентификатор тега и ссылается на столбец ID в таблице tag.

Эти ключи устанавливают отношения между таблицами.

Ниже таблицы post_tag вы создаете модель Tag, которая представляет собой таблицу, в которой вы будете хранить ваши теги. Эта таблица тегов имеет два столбца:

  • id: Идентификатор тега.
  • name: Название тега.

Вы используете название тега в специальном методе __repr__(), чтобы дать каждому объекту тега четкое строковое представление для целей отладки.

Вы добавляете переменную класса tags в модель Post. Вы используете метод db.relationship(), передавая ему имя модели тегов (Tag в данном случае).

Вы передаете ассоциативную таблицу post_tag параметру secondary, чтобы установить отношение многие ко многим между постами и тегами.

Вы используете параметр backref, чтобы добавить обратную ссылку, которая ведет себя как столбец, в модель Tag. Таким образом, вы можете получить доступ к постам тега через tag.posts и к тегам поста через post.tags. Пример этого будет показан позже.

Затем отредактируйте программу на Python init_db.py, чтобы изменить базу данных, добавив ассоциативную таблицу post_tag и таблицу тегов, которая будет основана на модели Tag:

  1. nano init_db.py

Измените файл следующим образом:

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)  # Отметьте первый пост как 'животные'
post1.tags.append(tag4)  # Отметьте первый пост как 'письменное творчество'
post3.tags.append(tag3)  # Отметьте третий пост как 'готовка'
post3.tags.append(tag2)  # Отметьте третий пост как 'техника'
post3.tags.append(tag4)  # Отметьте третий пост как 'письменное творчество'


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

Сохраните и закройте файл.

Здесь вы импортируете модель Tag. Вы удаляете все из базы данных, используя функцию db.drop_all(), чтобы безопасно добавить таблицы тегов и post_tag и избежать общих проблем, связанных с добавлением новых таблиц в базу данных. Затем вы создаете все таблицы заново, используя функцию db.create_all().

После кода из предыдущего урока, объявляющего посты и комментарии, вы используете модель Tag, чтобы создать четыре тега.

Затем вы добавляете теги к постам, используя атрибут tags, который был добавлен через строку tags = db.relationship('Tag', secondary=post_tag, backref='posts') в файле app.py. Вы присваиваете теги постам, используя метод append(), аналогичный спискам Python.

Затем вы добавляете созданные вами теги в сеанс базы данных, используя функцию db.session.add_all().

Примечание:

Функция db.create_all() не пересоздает и не обновляет таблицу, если она уже существует. Например, если вы измените свою модель, добавив новый столбец, и запустите функцию db.create_all(), изменение, которое вы внесли в модель, не будет применено к таблице, если таблица уже существует в базе данных. Решение состоит в удалении всех существующих таблиц базы данных с помощью функции db.drop_all(), а затем их повторном создании с помощью функции db.create_all(), как показано в файле init_db.py.

Этот процесс применит изменения, которые вы внесете в ваши модели, но также удалит все существующие данные в базе данных. Чтобы обновить базу данных и сохранить существующие данные, вам нужно использовать миграцию схемы, которая позволяет изменять ваши таблицы и сохранять данные. Вы можете использовать расширение Flask-Migrate для выполнения миграции схемы SQLAlchemy через интерфейс командной строки Flask.

Запустите программу init_db.py, чтобы применить изменения к базе данных:

  1. python init_db.py

Программа должна успешно выполниться без вывода. Если вы видите ошибку, убедитесь, что вы правильно внесли изменения в файл init_db.py.

Чтобы посмотреть на посты и теги, которые в настоящее время находятся в базе данных, откройте оболочку Flask:

  1. flask shell

Выполните следующий код Python, который перебирает посты и теги:

from app import Post

posts = Post.query.all()

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

Здесь вы импортируете модель Post из файла app.py. Вы запрашиваете таблицу постов и извлекаете все посты из базы данных. Вы перебираете посты и выводите заголовок поста и список тегов, связанных с каждым постом.

Вы получите вывод, аналогичный следующему:

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

Вы можете получить доступ к названиям тегов, используя tag.name, как показано в следующем примере, который можно запустить с помощью оболочки 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)

Здесь, помимо вывода заголовка поста, вы также перебираете теги каждого поста и выводите название тега.

Вы получите вывод, аналогичный следующему:

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

Как видите, теги, которые вы добавили к сообщениям в программе init_db.py, правильно связаны с сообщениями, с которыми они были помечены.

Чтобы увидеть демонстрацию того, как получить доступ к сообщениям, помеченным определенным тегом через tag.posts, выполните следующий код в оболочке 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)

Вы импортируете модель Tag. Затем вы используете метод filter_by() на атрибуте query, передавая ему параметр name, чтобы получить тег writing по его имени, и получаете первый результат с помощью метода first(). Вы сохраняете объект тега в переменной с именем writing_tag. Для получения дополнительной информации о методе filter_by см. Шаг 4 руководства “Как использовать Flask-SQLAlchemy для взаимодействия с базами данных в приложении Flask”.

Вы перебираете сообщения, помеченные тегом writing, к которым вы получаете доступ через writing_tag.posts. Вы печатаете заголовок сообщения, содержимое и список имен тегов, который вы создаете с помощью спискового включения на основе тегов сообщения, к которым вы получаете доступ через post.tags.

Вы получите выходные данные, аналогичные следующим:

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

Здесь вы видите два сообщения, помеченных тегом writing, и имена тегов отображаются в виде списка Python.

Теперь вы можете получать доступ к сообщениям и их тегам и получать доступ к сообщениям с определенным тегом.

Вы добавили модель базы данных, которая представляет собой таблицу тегов. Вы связали посты и теги с помощью таблицы ассоциаций и вставили несколько тегов в базу данных, пометив посты ими. Вы получили доступ к постам и их тегам, а также к постам по отдельному тегу. Затем вы будете использовать оболочку Flask для добавления новых постов и новых тегов, связывая теги с постами, и узнаете, как удалять теги из поста.

Шаг 3 — Управление данными в отношении Многие-ко-многим

На этом шаге вы будете использовать оболочку Flask для добавления новых постов в базу данных, добавления тегов и связи между постами и тегами. Вы получите доступ к постам с их тегами и увидите, как отделять один элемент от другого в отношениях Многие-ко-многим.

Сначала, с активированной средой программирования, откройте оболочку Flask, если еще не сделали этого:

  1. flask shell

Затем добавьте несколько постов и тегов:

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

Это создает два поста и три тега. Вы метите посты их соответствующими тегами и используете метод add_all() для добавления вновь созданных элементов в сеанс базы данных. Затем фиксируете изменения и применяете их к базе данных с помощью метода commit().

Затем используйте оболочку Flask, чтобы получить все посты и их теги, как вы делали на предыдущем шаге:

posts = Post.query.all()

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

Вы получите вывод, аналогичный следующему:

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

Вы увидите, что посты были добавлены вместе с их тегами.

Для демонстрации того, как разорвать связь между двумя элементами в базе данных с соотношением многие ко многим, предположим, что сообщение Пост Третий больше не касается кулинарии, поэтому вам нужно удалить тег cooking из него.

Сначала получите сообщение и тег, который вы хотите удалить:

  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)

Здесь вы извлекаете сообщение с заголовком Пост Третий, используя метод filter_by(). Вы получаете тег cooking. Выводите заголовок сообщения, его теги и сообщения с тегом cooking.

Метод filter_by() возвращает объект запроса, и вы можете использовать метод all(), чтобы получить список всех результатов. Но поскольку в этом случае ожидается только один результат, вы используете метод first(), чтобы получить первый (и единственный) результат. Для получения более подробной информации о методах first() и all() ознакомьтесь с Шаг 4. Как использовать Flask-SQLAlchemy для взаимодействия с базами данных в приложении Flask.

Вы получите следующий вывод:

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

Здесь вы видите заголовок сообщения, его теги и список сообщений с тегом cooking.

Чтобы удалить тег cooking из сообщения, используйте метод remove() следующим образом:

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

Здесь вы используете метод remove(), чтобы разорвать связь между тегом cooking и сообщением. Затем вы используете метод db.session.commit(), чтобы применить изменения к базе данных.

Вы получите выходные данные, подтверждающие, что тег был удален из сообщения:

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

Как видите, тег cooking больше не находится в списке post.tags, и сообщение было удалено из списка tag.posts.

Выйдите из оболочки Flask:

  1. exit()

Вы добавили новые сообщения и теги. Вы пометили сообщения, и вы удалили теги из сообщений. Далее вы отобразите теги каждого сообщения на главной странице вашего блога Flask.

Шаг 4 — Отображение тегов под каждым сообщением

На этом этапе вы измените шаблон индекса, чтобы отображать теги под каждым сообщением.

Сначала посмотрите на текущую главную страницу блога Flask.

При активированной среде программирования сообщите Flask о приложении (app.py в данном случае), используя переменную среды FLASK_APP. Затем установите переменную среды FLASK_ENV в значение development, чтобы запустить приложение в режиме разработки:

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

Затем запустите приложение:

  1. flask run

При запущенном сервере разработки перейдите по следующему URL в вашем браузере:

http://127.0.0.1:5000/

Вы увидите страницу, подобную следующей:

Оставьте запущенный сервер разработки и откройте новое окно терминала.

Вам нужно отображать теги каждого поста на двух страницах: под каждым постом на странице индекса и под содержимым поста на странице поста. Вы будете использовать один и тот же код для отображения тегов. Чтобы избежать повторения кода, вы будете использовать макрос Jinja, который ведет себя как функция Python. Макрос содержит динамический HTML-код, который может быть отображен там, где вы вызываете макрос, и редактирование его применяет изменения там, где он был вызван, что делает код многократно используемым.

Сначала откройте новый файл с именем macros.html в вашем каталоге templates:

  1. nano templates/macros.html

Добавьте в него следующий код:

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

Сохраните и закройте файл.

Здесь вы используете ключевое слово macro для объявления макроса с именем display_tags() с параметром post. Вы используете тег <div>, в котором отображается заголовок <h4>. Вы используете цикл for для перебора тегов объекта поста, который будет передан в качестве аргумента макросу при его вызове, аналогично передаче аргумента в вызове функции Python. Вы получаете теги через post.tags. Вы отображаете имя тега внутри тега <a>. Позже вы измените значение атрибута href, чтобы ссылаться на страницу тега, которую вы создадите, где будут отображаться все посты с определенным тегом. Вы указываете конец макроса с помощью ключевого слова endmacro.

Далее, чтобы отображать теги под каждым постом на странице индекса, откройте файл шаблона index.html:

  1. nano templates/index.html

Сначала вам нужно импортировать макрос display_tags() из файла macros.html. Добавьте следующий импорт наверху выше строки {% extends 'base.html' %}:

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

Затем отредактируйте цикл for post in posts, вызвав макрос display_tags() следующим образом:

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

Сохраните и закройте файл.

Вы вызываете макрос display_tags(), передавая ему объект post. Это отобразит названия тегов под каждым постом.

Обновите главную страницу в браузере, и вы увидите теги под каждым постом, как показано на следующем изображении:

Затем добавьте теги под содержанием поста на странице поста. Откройте файл шаблона post.html:

  1. nano templates/post.html

Сначала импортируйте макрос display_tags вверху:

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

Затем вызовите макрос display_tags(), передав ему объект post под содержанием поста и выше тега <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>

Сохраните и закройте файл.

Теперь перейдите на страницу поста:

http://127.0.0.1:5000/2

Вы увидите, что теги отображаются таким же образом, как теги, отображаемые на главной странице.

Вы отобразили теги, добавленные к постам под каждым постом. Далее вы добавите новый маршрут к вашему Flask-приложению, который отображает все посты, помеченные определенным тегом. Затем вы сделаете функциональными ссылки на теги, отображаемые на этом этапе.

Шаг 5 — Отображение тегов и их записей

На этом этапе вы добавите маршрут и шаблон к вашему веб-приложению для отображения тегов, которые у вас есть в базе данных, и их записей.

Сначала вы добавите маршрут для отображения записей каждого тега. Например, маршрут /tags/tag_name/ будет показывать страницу, на которой отображаются все записи с тегом с именем tag_name.

Откройте app.py для редактирования:

  1. nano app.py

Добавьте следующий маршрут в конце файла:

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)

Сохраните и закройте файл.

Здесь вы используете переменную URL с именем tag_name, которая определяет тег и записи, отмеченные этим тегом, которые будут отображены на странице тега. Имя тега передается в функцию представления tag() через параметр tag_name, который вы используете на методе filter_by() для запроса тега. Вы используете first_or_404(), чтобы получить объект тега и сохранить его в переменной с именем tag, или чтобы ответить с сообщением об ошибке 404 Not Found, если тег с указанным именем не существует в базе данных.

Затем вы отображаете файл шаблона с именем tag.html, передавая ему объект tag.

Откройте новый templates/tag.html для редактирования:

  1. nano templates/tag.html

Добавьте следующий код:

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

Сохраните и закройте файл.

Вы импортируете макрос display_tags() из файла macros.html и расширяете базовый шаблон.

В блоке контента вы задаете заголовок в качестве названия тега. Затем вы проходите по постам, помеченным данным тегом, к которым обращаетесь через tag.posts. Вы отображаете идентификатор поста, заголовок поста и содержимое поста. Затем вызываете макрос display_tags(), чтобы отобразить все теги постов.

При запущенном сервере разработки перейдите по следующему URL:

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

Это страница для тега writing. Как видите, отображаются все посты, помеченные тегом writing:

Теперь отредактируйте макрос display_tags(), чтобы теговые ссылки стали функциональными. Откройте файл macros.html:

  1. nano templates/macros.html

Измените значение атрибута href следующим образом:

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

Сохраните и закройте файл.

Обновите страницы, где был использован макрос display_tags(), и вы увидите, что теговые ссылки теперь функционируют:

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

Как видите, использование макросов Jinja позволяет вам повторно использовать код, и редактирование макроса применяет изменения ко множеству шаблонов.

Вы добавили страницу для отдельных тегов, где пользователи могут просматривать все посты, помеченные определенным тегом, а теги под постами теперь ведут на эту новую страницу.

Заключение

Функция тегов, которую вы добавили в свою систему блогов, демонстрирует, как управлять отношениями многие ко многим с использованием расширения Flask-SQLAlchemy. Вы узнали, как создавать связи между двумя связанными таблицами, используя ассоциативную таблицу (также называемую таблицей объединения), ассоциировать запись с другой, добавлять запись в базу данных, а также получать доступ к данным и разрывать связь с записью.

Если вы хотите узнать больше о Flask, ознакомьтесь с другими учебными пособиями в серии Как создавать веб-приложения с Flask.

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