Flask-SQLAlchemy로 다대다 데이터베이스 관계 사용하는 방법

저자는 자유 및 오픈 소스 기금기부를 위한 쓰기 프로그램의 일환으로 기부할 대상으로 선택했습니다.

소개

Flask는 파이썬 언어로 웹 애플리케이션을 만들기 위한 유용한 도구와 기능을 제공하는 가벼운 Python 웹 프레임워크입니다. SQLAlchemy은 관계형 데이터베이스에 대한 효율적이고 고성능의 데이터베이스 액세스를 제공하는 SQL 툴킷입니다. 이는 SQLite, MySQL 및 PostgreSQL과 같은 여러 데이터베이스 엔진과 상호 작용하는 방법을 제공합니다. 데이터베이스의 SQL 기능에 액세스할 수 있습니다. 또한 파이썬 객체 및 메서드를 사용하여 쿼리를 수행하고 데이터를 처리할 수 있도록 하는 객체 관계 매퍼(ORM)도 제공합니다. Flask-SQLAlchemy는 Flask와 SQLAlchemy를 함께 사용하기 쉽게 해주는 Flask 확장 기능으로, SQLAlchemy를 통해 Flask 애플리케이션에서 데이터베이스와 상호 작용하는 도구와 방법을 제공합니다.

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-SQLAlchemy를 사용하여 보여주기 위해, 이전 튜토리얼의 애플리케이션 코드를 사용할 것입니다. 해당 코드는 게시물을 추가하고 표시하며, 게시물에 댓글을 달고, 기존 댓글을 읽고 삭제할 수 있는 블로깅 시스템입니다.

저장소를 복제하고 다음 명령을 사용하여 flask-slqa-bloggyflask_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는 모드를 지정합니다. 개발 모드로 설정하여 애플리케이션을 디버그할 수 있습니다. 제품 환경에서는 이 모드를 사용하지 않도록 주의하십시오.

그런 다음 Flask 셸을 열어 데이터베이스 테이블을 생성합니다:

  1. flask shell

그런 다음 db Flask-SQLAlchemy 데이터베이스 객체, 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/는 데이터베이스의 두 번째 게시물 및 해당 댓글의 세부 정보를 표시합니다.
  • /comments/: 데이터베이스의 모든 댓글을 표시하고 각 댓글이 게시된 게시물로 연결된 페이지입니다.
  • /comments/<int:comment_id>/delete: 댓글 삭제 버튼을 통해 댓글을 삭제하는 경로입니다.

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

생명과 죽음에 대한 게시물을 lifedeath 태그로 태그하려고 가정해 보겠습니다. 다음과 같이 게시물 테이블에 새로운 행을 추가하여 수행할 수 있습니다:

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

이 접근 방식은 각 열이 하나의 값만 가져야 하기 때문에 작동하지 않습니다. 여러 값을 가지면 데이터 추가 및 업데이트와 같은 기본 작업이 번거롭고 느려집니다. 대신, 관련된 테이블의 주 키를 참조하는 세 번째 테이블이 있어야 합니다. 이 테이블은 종종 연관 테이블 또는 조인 테이블이라고 하며, 각 테이블의 각 항목의 ID를 저장합니다.

게시물과 태그 사이를 연결하는 연관 테이블의 예제는 다음과 같습니다:

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

첫 번째 행에서 ID가 1인 게시물(즉, 생명과 죽음에 대한 게시물)은 ID가 1인 태그(life)와 관련이 있습니다. 두 번째 행에서 동일한 게시물은 ID가 2인 태그(death)와도 관련이 있습니다. 이는 게시물이 lifedeath 태그로 태그되었음을 의미합니다. 마찬가지로 각 게시물에 여러 태그를 지정할 수 있습니다.

이제, app.py 파일을 수정하여 태그를 저장하는 데 사용할 테이블을 나타내는 새로운 데이터베이스 모델을 추가합니다. 또한 게시물과 태그를 연결하는 post_tag라는 연관 테이블을 추가합니다.

먼저, 게시물과 태그 간의 관계를 설정하려면 app.py를 엽니다:

  1. nano app.py

db 객체 아래에 post_tag 테이블과 Tag 모델을 추가하고 Post 모델 위에 넣은 후, Post 모델에 tags 관계 의사 열을 추가하여 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 테이블의 ID 열을 참조합니다.
  • tag_id: 태그 ID를 나타내는 정수 외래 키로, tag 테이블의 ID 열을 참조합니다.

이러한 키는 테이블 간의 관계를 설정합니다.

post_tag 테이블 아래에는 각 태그를 저장할 Tag 모델을 생성합니다. 이 태그 테이블에는 두 개의 열이 있습니다:

  • id: 태그의 ID입니다.
  • name: 태그의 이름입니다.

각 태그 객체에 대한 명확한 문자열 표현을 디버깅 목적으로 제공하기 위해 특별한 __repr__() 메서드에서 태그의 이름을 사용합니다.

tags 클래스 변수를 Post 모델에 추가합니다. db.relationship() 메서드를 사용하여 태그 모델의 이름(Tag 이 경우)을 전달합니다.

secondary 매개변수에 post_tag 연관 테이블을 전달하여 게시물과 태그 간의 다대다 관계를 설정합니다.

backref 매개변수를 사용하여 열처럼 동작하는 역참조를 Tag 모델에 추가합니다. 이렇게 하면 태그의 게시물을 tag.posts를 통해 액세스하고 게시물의 태그를 post.tags를 통해 액세스할 수 있습니다. 이후에 이를 보여주는 예제가 있습니다.

다음으로, 데이터베이스를 수정하기 위해 init_db.py Python 프로그램을 편집하여 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 모델을 사용하여 네 가지 태그를 만듭니다.

그런 다음 app.py 파일의 tags = db.relationship('Tag', secondary=post_tag, backref='posts') 라인을 통해 추가된 tags 속성을 사용하여 게시물에 태그를 추가합니다. 파이썬 리스트와 유사한 append() 메서드를 사용하여 게시물에 태그를 할당합니다.

다음으로, db.session.add_all() 함수를 사용하여 생성한 태그를 데이터베이스 세션에 추가합니다.

참고:

db.create_all() 함수는 이미 존재하는 경우 테이블을 다시 생성하거나 업데이트하지 않습니다. 예를 들어, 새 열을 추가하여 모델을 수정하고 db.create_all() 함수를 실행하면 데이터베이스에 이미 테이블이 있는 경우 모델에 대한 변경 사항이 테이블에 적용되지 않습니다. 해결책은 db.drop_all() 함수를 사용하여 모든 기존 데이터베이스 테이블을 삭제하고 db.create_all() 함수를 사용하여 다시 만드는 것입니다. 이는 init_db.py 파일에서 보여주는 것처럼 진행됩니다.

이 프로세스는 모델에 대한 수정 사항을 적용하지만 데이터베이스의 모든 기존 데이터도 삭제합니다. 데이터베이스를 업데이트하고 기존 데이터를 보존하려면 스키마 마이그레이션을 사용해야합니다. 이를 통해 테이블을 수정하고 데이터를 보존할 수 있습니다.

SQLAlchemy 스키마 마이그레이션을 Flask 명령줄 인터페이스를 통해 수행하기 위해 Flask-Migrate 확장을 사용할 수 있습니다.

  1. python init_db.py

데이터베이스에 변경 사항을 적용하려면 init_db.py 프로그램을 실행하십시오:

프로그램은 출력없이 성공적으로 실행되어야합니다. 오류가 발생하면 init_db.py 파일을 올바르게 수정했는지 확인하십시오.

  1. flask shell

데이터베이스에 현재 있는 게시물 및 태그를 살펴보려면 Flask 셸을 엽니다:

from app import Post

posts = Post.query.all()

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

다음 파이썬 코드를 실행하여 게시물과 태그를 순회합니다:

여기에서는 app.py에서 Post 모델을 가져옵니다. 게시물 테이블을 쿼리하고 데이터베이스의 모든 게시물을 가져옵니다. 게시물을 순회하고 각 게시물의 제목과 연관된 태그 목록을 출력합니다.

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

다음과 유사한 출력을 받게됩니다:

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 프로그램에 추가한 태그들은 해당 태그가 붙은 게시물과 올바르게 연결되어 있습니다.

특정 태그가 붙은 게시물에 액세스하는 방법을 시연하려면 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 모델을 가져옵니다. 그런 다음 query 속성에 name 매개변수를 전달하여 writing 태그를 가져오기 위해 filter_by() 메서드를 사용하고, first() 메서드를 사용하여 첫 번째 결과를 가져옵니다. 태그 객체를 writing_tag라는 변수에 저장합니다. filter_by 메서드에 대한 자세한 내용은 플라스크 애플리케이션에서 데이터베이스와 상호 작용하는 방법 튜토리얼의 4단계를 참조하십시오.

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 셸을 사용하여 새 게시물과 새 태그를 추가하고 태그와 게시물 간의 연결을 만들고, 게시물에서 태그를 제거하는 방법을 배울 것입니다.

다대다 관계에서 데이터 관리하기

이 단계에서는 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">] ---

게시물과 해당 태그가 추가된 것을 확인할 수 있습니다.

관계형 데이터베이스의 많은 대 다 대응 관계에서 두 항목 간의 관계를 해제하는 방법을 보여주기 위해, Post The Third 포스트가 더 이상 요리에 관한 것이 아니라고 가정해보겠습니다. 그러므로 해당 포스트에서 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() 메서드를 사용하여 Post The Third 제목의 포스트를 가져옵니다. cooking 태그를 얻습니다. 포스트의 제목, 해당 태그 및 cooking 태그가 지정된 포스트를 출력합니다.

filter_by() 메서드는 쿼리 객체를 반환하며, all() 메서드를 사용하여 모든 결과의 목록을 가져올 수 있습니다. 그러나 이 경우에는 하나의 결과만 예상하므로 first() 메서드를 사용하여 첫 번째(그리고 유일한) 결과를 가져옵니다. first()all() 메서드에 대한 자세한 내용은 Flask 응용 프로그램에서 데이터베이스와 상호 작용하기 위한 Flask-SQLAlchemy 사용하기 단계 4를 참조하십시오.

다음 출력을 받게 됩니다:

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 환경 변수를 사용하여 응용 프로그램(app.py인 경우)을 Flask에 알려주세요. 그런 다음 응용 프로그램을 개발 모드로 실행하기 위해 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 코드를 보유하며 매크로를 호출하는 위치마다 표시될 수 있으며 편집하면 호출된 모든 위치에 변경 사항이 적용되어 코드를 재사용할 수 있게합니다.

먼저, templates 디렉토리에 macros.html이라는 새 파일을 엽니다:

  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 속성 값의 끝을 지정합니다.

다음으로, 인덱스 페이지에서 각 게시물 아래에 태그를 표시하려면 index.html 템플릿 파일을 엽니다:

  1. nano templates/index.html

먼저, macros.html 파일에서 display_tags() 매크로를 가져와야 합니다. 다음과 같이 {% 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 %}

파일을 저장하고 닫으십시오.

이제 post 객체를 전달하여 display_tags() 매크로를 호출합니다. 이렇게 하면 각 게시물 아래에 태그 이름이 표시됩니다.

브라우저에서 새로 고침하면 각 게시물 아래에 태그가 표시되는 것을 확인할 수 있습니다. 다음 이미지와 같이:

다음으로, 게시물 페이지의 게시물 내용 아래에 태그를 추가합니다. post.html 템플릿 파일을 엽니다:

  1. nano templates/post.html

먼저, 맨 위에 display_tags 매크로를 가져옵니다:

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

그런 다음, 게시물 내용 아래에 <hr> 태그 위에 post 객체를 전달하여 display_tags() 매크로를 호출합니다:

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()를 사용하여 주어진 이름의 태그가 데이터베이스에 존재하지 않는 경우 404 Not Found 오류 메시지로 응답하거나 태그 객체를 가져와 tag 변수에 저장합니다.

그런 다음 tag 객체를 전달하여 tag.html이라는 템플릿 파일을 렌더링합니다.

새로운 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 %}

파일을 저장하고 닫습니다.

당신은 macros.html에서 display_tags() 매크로를 가져와서 기본 템플릿을 확장합니다.

컨텐츠 블록에서는 제목으로 태그 이름이 포함된 제목을 설정합니다. 그런 다음 주어진 태그로 태그가 지정된 포스트를 루프로 반복하고, 이를 tag.posts로 접근합니다. 포스트 ID, 포스트 제목 및 포스트 내용을 표시합니다. 그런 다음 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